lms.backend.blackboard.backend

  1# pylint: disable=abstract-method
  2
  3import typing
  4
  5import edq.net.request
  6
  7import lms.backend.blackboard.model
  8import lms.model.backend
  9import lms.model.constants
 10import lms.model.courses
 11import lms.model.users
 12import lms.util.net
 13import lms.util.parse
 14
 15class BlackboardBackend(lms.model.backend.APIBackend):
 16    """ An API backend for the Blackboard Learn LMS. """
 17
 18    def __init__(self,
 19            server: str,
 20            auth_user: typing.Union[str, None] = None,
 21            auth_password: typing.Union[str, None] = None,
 22            **kwargs: typing.Any) -> None:
 23        super().__init__(server, lms.model.constants.BACKEND_TYPE_BLACKBOARD, **kwargs)
 24
 25        if (auth_user is None):
 26            raise ValueError("Blackboard backends require a username.")
 27
 28        if (auth_password is None):
 29            raise ValueError("Blackboard backends require a password.")
 30
 31        self._username = auth_user
 32        """ The username to authenticate with. """
 33
 34        self._password = auth_password
 35        """ The password to authenticate with. """
 36
 37        self._session_headers: typing.Union[typing.Dict[str, typing.Any], None] = None
 38        """ The headers (e.g., cookies) for our logged in Blackboard session. """
 39
 40    def _login(self) -> None:
 41        """ Try to login to the Blackboard server. """
 42
 43        # Check if we are already logged in.
 44        if (self._session_headers is not None):
 45            return
 46
 47        response, _ = edq.net.request.make_get(self.server)
 48        cookies, router_params = self._parse_bb_cookies(response.headers.get('set-cookie', None))
 49
 50        new_cookies = {
 51            'BbRouter': cookies['bbrouter']
 52        }
 53
 54        text_cookies = '; '.join(['='.join(items) for items in new_cookies.items()])
 55
 56        headers = {
 57            'cookie': text_cookies,
 58        }
 59        data = {
 60            'user_id': self._username,
 61            'password': self._password,
 62            'blackboard.platform.security.NonceUtil.nonce.ajax': router_params['xsrf'],
 63        }
 64
 65        response, _ = edq.net.request.make_post(self.server + '/webapps/login/',
 66                headers = headers, data = data,
 67                # Don't store the nonce in exchanges.
 68                params_to_skip = ['blackboard.platform.security.NonceUtil.nonce.ajax'],
 69                allow_redirects = False)
 70
 71        cookies, router_params = self._parse_bb_cookies(response.headers.get('set-cookie', None))
 72        if ('sessionId' not in router_params):
 73            raise ValueError(f"Could not log into Blackboard server ({self.server}) with user '{self._username}'. Is username/password correct?")
 74
 75        self._session_headers = {
 76            'cookie': response.headers.get('set-cookie', None),
 77            'x-blackboard-xsrf': router_params['xsrf'],
 78            # Insert a header to identify the user.
 79            'edq-lms-blackboard-user': self._username,
 80        }
 81
 82    def _parse_bb_cookies(self, text_cookies: typing.Union[str, None]) -> typing.Tuple[typing.Dict[str, typing.Any], typing.Dict[str, str]]:
 83        """
 84        Parse out the Blackboard cookies and return two dicts:
 85         - All cookies (with lower case keys).
 86         - The router params/cookies (specific to Blackboard).
 87        """
 88
 89        cookies: typing.Dict[str, typing.Any] = {}
 90        router_params: typing.Dict[str, str] = {}
 91
 92        # If we are testing, return fake cookies.
 93        if (self.is_testing()):
 94            cookies['bbrouter'] = 'xsrf:abc,sessionId:9999999'
 95            router_params['xsrf'] = 'abc'
 96            router_params['sessionId'] = '9999999'
 97
 98            return cookies, router_params
 99
100        cookies = lms.util.net.parse_cookies(text_cookies)
101
102        router_text = cookies.get('bbrouter', None)
103        if (isinstance(router_text, str)):
104            for text in router_text.split(','):
105                key, value = text.split(':', maxsplit = 1)
106                router_params[key] = value
107
108        return cookies, router_params
109
110    def courses_list(self,
111            **kwargs: typing.Any) -> typing.List[lms.model.courses.Course]:
112        self._login()
113
114        url = self.server + '/learn/api/public/v3/courses'
115        data = {
116            'availability.available': 'Yes',
117        }
118        response, _ = edq.net.request.make_get(url, headers = self._session_headers, data = data)
119
120        courses = []
121        for raw_course in response.json().get('results', []):
122            courses.append(lms.backend.blackboard.model.course(raw_course))
123
124        courses.sort()
125
126        return courses
127
128    def courses_users_list(self,
129            course_id: str,
130            **kwargs: typing.Any) -> typing.List[lms.model.users.CourseUser]:
131        parsed_course_id = lms.util.parse.required_int(course_id, 'course_id')
132
133        self._login()
134
135        url = self.server + '/learn/api/public/v1/courses/{course_id}/users'
136        url = url.format(course_id = lms.backend.blackboard.model.format_id(parsed_course_id))
137
138        data = {
139            'expand': 'user',
140        }
141
142        response, _ = edq.net.request.make_get(url, headers = self._session_headers, data = data)
143
144        users = []
145        for raw_user in response.json().get('results', []):
146            users.append(lms.backend.blackboard.model.course_user(raw_user))
147
148        users.sort()
149
150        return users
class BlackboardBackend(lms.model.backend.APIBackend):
 16class BlackboardBackend(lms.model.backend.APIBackend):
 17    """ An API backend for the Blackboard Learn LMS. """
 18
 19    def __init__(self,
 20            server: str,
 21            auth_user: typing.Union[str, None] = None,
 22            auth_password: typing.Union[str, None] = None,
 23            **kwargs: typing.Any) -> None:
 24        super().__init__(server, lms.model.constants.BACKEND_TYPE_BLACKBOARD, **kwargs)
 25
 26        if (auth_user is None):
 27            raise ValueError("Blackboard backends require a username.")
 28
 29        if (auth_password is None):
 30            raise ValueError("Blackboard backends require a password.")
 31
 32        self._username = auth_user
 33        """ The username to authenticate with. """
 34
 35        self._password = auth_password
 36        """ The password to authenticate with. """
 37
 38        self._session_headers: typing.Union[typing.Dict[str, typing.Any], None] = None
 39        """ The headers (e.g., cookies) for our logged in Blackboard session. """
 40
 41    def _login(self) -> None:
 42        """ Try to login to the Blackboard server. """
 43
 44        # Check if we are already logged in.
 45        if (self._session_headers is not None):
 46            return
 47
 48        response, _ = edq.net.request.make_get(self.server)
 49        cookies, router_params = self._parse_bb_cookies(response.headers.get('set-cookie', None))
 50
 51        new_cookies = {
 52            'BbRouter': cookies['bbrouter']
 53        }
 54
 55        text_cookies = '; '.join(['='.join(items) for items in new_cookies.items()])
 56
 57        headers = {
 58            'cookie': text_cookies,
 59        }
 60        data = {
 61            'user_id': self._username,
 62            'password': self._password,
 63            'blackboard.platform.security.NonceUtil.nonce.ajax': router_params['xsrf'],
 64        }
 65
 66        response, _ = edq.net.request.make_post(self.server + '/webapps/login/',
 67                headers = headers, data = data,
 68                # Don't store the nonce in exchanges.
 69                params_to_skip = ['blackboard.platform.security.NonceUtil.nonce.ajax'],
 70                allow_redirects = False)
 71
 72        cookies, router_params = self._parse_bb_cookies(response.headers.get('set-cookie', None))
 73        if ('sessionId' not in router_params):
 74            raise ValueError(f"Could not log into Blackboard server ({self.server}) with user '{self._username}'. Is username/password correct?")
 75
 76        self._session_headers = {
 77            'cookie': response.headers.get('set-cookie', None),
 78            'x-blackboard-xsrf': router_params['xsrf'],
 79            # Insert a header to identify the user.
 80            'edq-lms-blackboard-user': self._username,
 81        }
 82
 83    def _parse_bb_cookies(self, text_cookies: typing.Union[str, None]) -> typing.Tuple[typing.Dict[str, typing.Any], typing.Dict[str, str]]:
 84        """
 85        Parse out the Blackboard cookies and return two dicts:
 86         - All cookies (with lower case keys).
 87         - The router params/cookies (specific to Blackboard).
 88        """
 89
 90        cookies: typing.Dict[str, typing.Any] = {}
 91        router_params: typing.Dict[str, str] = {}
 92
 93        # If we are testing, return fake cookies.
 94        if (self.is_testing()):
 95            cookies['bbrouter'] = 'xsrf:abc,sessionId:9999999'
 96            router_params['xsrf'] = 'abc'
 97            router_params['sessionId'] = '9999999'
 98
 99            return cookies, router_params
100
101        cookies = lms.util.net.parse_cookies(text_cookies)
102
103        router_text = cookies.get('bbrouter', None)
104        if (isinstance(router_text, str)):
105            for text in router_text.split(','):
106                key, value = text.split(':', maxsplit = 1)
107                router_params[key] = value
108
109        return cookies, router_params
110
111    def courses_list(self,
112            **kwargs: typing.Any) -> typing.List[lms.model.courses.Course]:
113        self._login()
114
115        url = self.server + '/learn/api/public/v3/courses'
116        data = {
117            'availability.available': 'Yes',
118        }
119        response, _ = edq.net.request.make_get(url, headers = self._session_headers, data = data)
120
121        courses = []
122        for raw_course in response.json().get('results', []):
123            courses.append(lms.backend.blackboard.model.course(raw_course))
124
125        courses.sort()
126
127        return courses
128
129    def courses_users_list(self,
130            course_id: str,
131            **kwargs: typing.Any) -> typing.List[lms.model.users.CourseUser]:
132        parsed_course_id = lms.util.parse.required_int(course_id, 'course_id')
133
134        self._login()
135
136        url = self.server + '/learn/api/public/v1/courses/{course_id}/users'
137        url = url.format(course_id = lms.backend.blackboard.model.format_id(parsed_course_id))
138
139        data = {
140            'expand': 'user',
141        }
142
143        response, _ = edq.net.request.make_get(url, headers = self._session_headers, data = data)
144
145        users = []
146        for raw_user in response.json().get('results', []):
147            users.append(lms.backend.blackboard.model.course_user(raw_user))
148
149        users.sort()
150
151        return users

An API backend for the Blackboard Learn LMS.

BlackboardBackend( server: str, auth_user: Optional[str] = None, auth_password: Optional[str] = None, **kwargs: Any)
19    def __init__(self,
20            server: str,
21            auth_user: typing.Union[str, None] = None,
22            auth_password: typing.Union[str, None] = None,
23            **kwargs: typing.Any) -> None:
24        super().__init__(server, lms.model.constants.BACKEND_TYPE_BLACKBOARD, **kwargs)
25
26        if (auth_user is None):
27            raise ValueError("Blackboard backends require a username.")
28
29        if (auth_password is None):
30            raise ValueError("Blackboard backends require a password.")
31
32        self._username = auth_user
33        """ The username to authenticate with. """
34
35        self._password = auth_password
36        """ The password to authenticate with. """
37
38        self._session_headers: typing.Union[typing.Dict[str, typing.Any], None] = None
39        """ The headers (e.g., cookies) for our logged in Blackboard session. """
def courses_list(self, **kwargs: Any) -> List[lms.model.courses.Course]:
111    def courses_list(self,
112            **kwargs: typing.Any) -> typing.List[lms.model.courses.Course]:
113        self._login()
114
115        url = self.server + '/learn/api/public/v3/courses'
116        data = {
117            'availability.available': 'Yes',
118        }
119        response, _ = edq.net.request.make_get(url, headers = self._session_headers, data = data)
120
121        courses = []
122        for raw_course in response.json().get('results', []):
123            courses.append(lms.backend.blackboard.model.course(raw_course))
124
125        courses.sort()
126
127        return courses

List the courses associated with the context user.

def courses_users_list(self, course_id: str, **kwargs: Any) -> List[lms.model.users.CourseUser]:
129    def courses_users_list(self,
130            course_id: str,
131            **kwargs: typing.Any) -> typing.List[lms.model.users.CourseUser]:
132        parsed_course_id = lms.util.parse.required_int(course_id, 'course_id')
133
134        self._login()
135
136        url = self.server + '/learn/api/public/v1/courses/{course_id}/users'
137        url = url.format(course_id = lms.backend.blackboard.model.format_id(parsed_course_id))
138
139        data = {
140            'expand': 'user',
141        }
142
143        response, _ = edq.net.request.make_get(url, headers = self._session_headers, data = data)
144
145        users = []
146        for raw_user in response.json().get('results', []):
147            users.append(lms.backend.blackboard.model.course_user(raw_user))
148
149        users.sort()
150
151        return users

List the users associated with the given course.

Inherited Members
lms.model.backend.APIBackend
server
backend_type
testing
is_testing
get_standard_headers
not_found
courses_get
courses_fetch
courses_assignments_get
courses_assignments_fetch
courses_assignments_list
courses_assignments_resolve_and_list
courses_assignments_scores_get
courses_assignments_scores_fetch
courses_assignments_scores_list
courses_assignments_scores_resolve_and_list
courses_assignments_scores_resolve_and_upload
courses_assignments_scores_upload
courses_gradebook_get
courses_gradebook_fetch
courses_gradebook_list
courses_gradebook_resolve_and_list
courses_gradebook_resolve_and_upload
courses_gradebook_upload
courses_groupsets_create
courses_groupsets_resolve_and_create
courses_groupsets_delete
courses_groupsets_resolve_and_delete
courses_groupsets_get
courses_groupsets_fetch
courses_groupsets_list
courses_groupsets_resolve_and_list
courses_groupsets_memberships_resolve_and_add
courses_groupsets_memberships_resolve_and_set
courses_groupsets_memberships_resolve_and_subtract
courses_groupsets_memberships_list
courses_groupsets_memberships_resolve_and_list
courses_groups_create
courses_groups_resolve_and_create
courses_groups_delete
courses_groups_resolve_and_delete
courses_groups_get
courses_groups_fetch
courses_groups_list
courses_groups_resolve_and_list
courses_groups_memberships_add
courses_groups_memberships_resolve_and_add
courses_groups_memberships_list
courses_groups_memberships_resolve_and_list
courses_groups_memberships_resolve_and_set
courses_groups_memberships_subtract
courses_groups_memberships_resolve_and_subtract
courses_syllabus_fetch
courses_syllabus_get
courses_users_get
courses_users_fetch
courses_users_resolve_and_list
courses_users_scores_get
courses_users_scores_fetch
courses_users_scores_list
courses_users_scores_resolve_and_list
parse_assignment_query
parse_assignment_queries
parse_course_query
parse_course_queries
parse_groupset_query
parse_groupset_queries
parse_group_query
parse_group_queries
parse_user_query
parse_user_queries
resolve_assignment_query
resolve_assignment_queries
resolve_course_query
resolve_course_queries
resolve_group_queries
resolve_group_query
resolve_groupset_queries
resolve_groupset_query
resolve_user_queries