ff-ltitoolkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. ff_ltitoolkit-0.1.0.dist-info/METADATA +98 -0
  2. ff_ltitoolkit-0.1.0.dist-info/RECORD +94 -0
  3. ff_ltitoolkit-0.1.0.dist-info/WHEEL +4 -0
  4. ff_ltitoolkit-0.1.0.dist-info/licenses/LICENSE +21 -0
  5. ltitoolkit/__init__.py +20 -0
  6. ltitoolkit/adapters/__init__.py +11 -0
  7. ltitoolkit/adapters/brightspace/__init__.py +35 -0
  8. ltitoolkit/adapters/brightspace/client.py +176 -0
  9. ltitoolkit/adapters/canvas/__init__.py +27 -0
  10. ltitoolkit/adapters/canvas/client.py +142 -0
  11. ltitoolkit/advantage/__init__.py +9 -0
  12. ltitoolkit/advantage/service.py +96 -0
  13. ltitoolkit/core/__init__.py +19 -0
  14. ltitoolkit/core/actions.py +6 -0
  15. ltitoolkit/core/assignments_grades.py +300 -0
  16. ltitoolkit/core/contrib/__init__.py +0 -0
  17. ltitoolkit/core/contrib/django/__init__.py +5 -0
  18. ltitoolkit/core/contrib/django/cookie.py +56 -0
  19. ltitoolkit/core/contrib/django/launch_data_storage/__init__.py +0 -0
  20. ltitoolkit/core/contrib/django/launch_data_storage/cache.py +10 -0
  21. ltitoolkit/core/contrib/django/lti1p3_tool_config/__init__.py +139 -0
  22. ltitoolkit/core/contrib/django/lti1p3_tool_config/admin.py +48 -0
  23. ltitoolkit/core/contrib/django/lti1p3_tool_config/apps.py +6 -0
  24. ltitoolkit/core/contrib/django/lti1p3_tool_config/migrations/0001_initial.py +168 -0
  25. ltitoolkit/core/contrib/django/lti1p3_tool_config/migrations/__init__.py +0 -0
  26. ltitoolkit/core/contrib/django/lti1p3_tool_config/models.py +185 -0
  27. ltitoolkit/core/contrib/django/message_launch.py +39 -0
  28. ltitoolkit/core/contrib/django/oidc_login.py +41 -0
  29. ltitoolkit/core/contrib/django/redirect.py +34 -0
  30. ltitoolkit/core/contrib/django/request.py +32 -0
  31. ltitoolkit/core/contrib/django/session.py +5 -0
  32. ltitoolkit/core/contrib/flask/__init__.py +7 -0
  33. ltitoolkit/core/contrib/flask/cookie.py +34 -0
  34. ltitoolkit/core/contrib/flask/launch_data_storage/__init__.py +0 -0
  35. ltitoolkit/core/contrib/flask/launch_data_storage/cache.py +9 -0
  36. ltitoolkit/core/contrib/flask/message_launch.py +32 -0
  37. ltitoolkit/core/contrib/flask/oidc_login.py +31 -0
  38. ltitoolkit/core/contrib/flask/redirect.py +34 -0
  39. ltitoolkit/core/contrib/flask/request.py +40 -0
  40. ltitoolkit/core/contrib/flask/session.py +5 -0
  41. ltitoolkit/core/contrib/py.typed +0 -0
  42. ltitoolkit/core/cookie.py +17 -0
  43. ltitoolkit/core/cookies_allowed_check.py +151 -0
  44. ltitoolkit/core/course_groups.py +115 -0
  45. ltitoolkit/core/deep_link.py +100 -0
  46. ltitoolkit/core/deep_link_resource.py +96 -0
  47. ltitoolkit/core/deployment.py +13 -0
  48. ltitoolkit/core/exception.py +16 -0
  49. ltitoolkit/core/grade.py +143 -0
  50. ltitoolkit/core/launch_data_storage/__init__.py +0 -0
  51. ltitoolkit/core/launch_data_storage/base.py +75 -0
  52. ltitoolkit/core/launch_data_storage/cache.py +43 -0
  53. ltitoolkit/core/launch_data_storage/session.py +29 -0
  54. ltitoolkit/core/lineitem.py +205 -0
  55. ltitoolkit/core/message_launch.py +828 -0
  56. ltitoolkit/core/message_validators/__init__.py +13 -0
  57. ltitoolkit/core/message_validators/abstract.py +25 -0
  58. ltitoolkit/core/message_validators/deep_link.py +34 -0
  59. ltitoolkit/core/message_validators/privacy_launch.py +40 -0
  60. ltitoolkit/core/message_validators/resource_message.py +21 -0
  61. ltitoolkit/core/message_validators/submission_review.py +45 -0
  62. ltitoolkit/core/names_roles.py +97 -0
  63. ltitoolkit/core/oidc_login.py +275 -0
  64. ltitoolkit/core/py.typed +0 -0
  65. ltitoolkit/core/redirect.py +24 -0
  66. ltitoolkit/core/registration.py +119 -0
  67. ltitoolkit/core/request.py +17 -0
  68. ltitoolkit/core/roles.py +109 -0
  69. ltitoolkit/core/service_connector.py +144 -0
  70. ltitoolkit/core/session.py +70 -0
  71. ltitoolkit/core/tool_config/__init__.py +4 -0
  72. ltitoolkit/core/tool_config/abstract.py +117 -0
  73. ltitoolkit/core/tool_config/dict.py +253 -0
  74. ltitoolkit/core/tool_config/json_file.py +100 -0
  75. ltitoolkit/core/tool_config/py.typed +0 -0
  76. ltitoolkit/core/utils.py +10 -0
  77. ltitoolkit/dynamic_registration/__init__.py +39 -0
  78. ltitoolkit/dynamic_registration/models.py +192 -0
  79. ltitoolkit/dynamic_registration/service.py +156 -0
  80. ltitoolkit/dynamic_registration/store.py +40 -0
  81. ltitoolkit/dynamic_registration/tool_conf.py +102 -0
  82. ltitoolkit/exceptions.py +42 -0
  83. ltitoolkit/fastapi/__init__.py +30 -0
  84. ltitoolkit/fastapi/cookie.py +53 -0
  85. ltitoolkit/fastapi/dynamic_registration.py +40 -0
  86. ltitoolkit/fastapi/message_launch.py +60 -0
  87. ltitoolkit/fastapi/oidc_login.py +47 -0
  88. ltitoolkit/fastapi/redirect.py +54 -0
  89. ltitoolkit/fastapi/request.py +77 -0
  90. ltitoolkit/fastapi/session.py +13 -0
  91. ltitoolkit/http.py +80 -0
  92. ltitoolkit/token/__init__.py +20 -0
  93. ltitoolkit/token/cache.py +47 -0
  94. ltitoolkit/token/service.py +165 -0
@@ -0,0 +1,13 @@
1
+ from .deep_link import DeepLinkMessageValidator
2
+ from .resource_message import ResourceMessageValidator
3
+ from .privacy_launch import PrivacyLaunchValidator
4
+ from .submission_review import SubmissionReviewLaunchValidator
5
+
6
+
7
+ def get_validators():
8
+ return [
9
+ DeepLinkMessageValidator(),
10
+ ResourceMessageValidator(),
11
+ PrivacyLaunchValidator(),
12
+ SubmissionReviewLaunchValidator(),
13
+ ]
@@ -0,0 +1,25 @@
1
+ from abc import ABCMeta, abstractmethod
2
+ from ..exception import LtiException
3
+
4
+
5
+ class MessageValidatorAbstract:
6
+ __metaclass__ = ABCMeta
7
+
8
+ @abstractmethod
9
+ def validate(self, jwt_body) -> bool:
10
+ raise NotImplementedError
11
+
12
+ @abstractmethod
13
+ def can_validate(self, jwt_body) -> bool:
14
+ raise NotImplementedError
15
+
16
+ def run_common_validators(self, jwt_body) -> None:
17
+ if not jwt_body.get("sub"):
18
+ raise LtiException("Must have a user (sub)")
19
+
20
+ if jwt_body.get("https://purl.imsglobal.org/spec/lti/claim/version") != "1.3.0":
21
+ raise LtiException("Incorrect version, expected 1.3.0")
22
+
23
+ roles = jwt_body.get("https://purl.imsglobal.org/spec/lti/claim/roles")
24
+ if roles is None:
25
+ raise LtiException("Missing Roles Claim")
@@ -0,0 +1,34 @@
1
+ from ..exception import LtiException
2
+ from .abstract import MessageValidatorAbstract
3
+
4
+
5
+ class DeepLinkMessageValidator(MessageValidatorAbstract):
6
+ def validate(self, jwt_body) -> bool:
7
+ self.run_common_validators(jwt_body)
8
+
9
+ if not jwt_body.get(
10
+ "https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings"
11
+ ):
12
+ raise LtiException("Missing Deep Linking Settings")
13
+
14
+ deep_link_settings = jwt_body.get(
15
+ "https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings"
16
+ )
17
+ if not deep_link_settings:
18
+ raise LtiException("Missing Deep Linking Return URL")
19
+
20
+ accept_types = deep_link_settings.get("accept_types")
21
+
22
+ if not isinstance(accept_types, list) or "ltiResourceLink" not in accept_types:
23
+ raise LtiException("Must support resource link placement types")
24
+
25
+ if not deep_link_settings.get("accept_presentation_document_targets"):
26
+ raise LtiException("Must support a presentation type")
27
+
28
+ return True
29
+
30
+ def can_validate(self, jwt_body) -> bool:
31
+ return (
32
+ jwt_body.get("https://purl.imsglobal.org/spec/lti/claim/message_type")
33
+ == "LtiDeepLinkingRequest"
34
+ )
@@ -0,0 +1,40 @@
1
+ from ..exception import LtiException
2
+ from .abstract import MessageValidatorAbstract
3
+
4
+
5
+ class PrivacyLaunchValidator(MessageValidatorAbstract):
6
+ """Validates the body of a LTI data privacy launch.
7
+
8
+ The launch must omit the context claim, and include
9
+ a for_user claim specifying the user that the launch
10
+ was made on behalf of.
11
+ """
12
+
13
+ def validate(self, jwt_body) -> bool:
14
+ self.run_common_validators(jwt_body)
15
+
16
+ if "https://purl.imsglobal.org/spec/lti/claim/resource_link" in jwt_body:
17
+ raise LtiException(
18
+ "Resource link claim must be omitted from a DataPrivacyLaunchRequest"
19
+ )
20
+
21
+ if "https://purl.imsglobal.org/spec/lti/claim/context" in jwt_body:
22
+ raise LtiException(
23
+ "Context claim must be omitted from a DataPrivacyLaunchRequest"
24
+ )
25
+
26
+ for_user_claim = jwt_body.get(
27
+ "https://purl.imsglobal.org/spec/lti/claim/for_user"
28
+ )
29
+ if for_user_claim is None:
30
+ raise LtiException(
31
+ "For user claim must be included in a DataPrivacyLaunchRequest"
32
+ )
33
+
34
+ return True
35
+
36
+ def can_validate(self, jwt_body) -> bool:
37
+ return (
38
+ jwt_body.get("https://purl.imsglobal.org/spec/lti/claim/message_type")
39
+ == "DataPrivacyLaunchRequest"
40
+ )
@@ -0,0 +1,21 @@
1
+ from ..exception import LtiException
2
+ from .abstract import MessageValidatorAbstract
3
+
4
+
5
+ class ResourceMessageValidator(MessageValidatorAbstract):
6
+ def validate(self, jwt_body) -> bool:
7
+ self.run_common_validators(jwt_body)
8
+
9
+ id_val = jwt_body.get(
10
+ "https://purl.imsglobal.org/spec/lti/claim/resource_link", {}
11
+ ).get("id")
12
+ if not id_val:
13
+ raise LtiException("Missing Resource Link Id")
14
+
15
+ return True
16
+
17
+ def can_validate(self, jwt_body) -> bool:
18
+ return (
19
+ jwt_body.get("https://purl.imsglobal.org/spec/lti/claim/message_type")
20
+ == "LtiResourceLinkRequest"
21
+ )
@@ -0,0 +1,45 @@
1
+ from ..exception import LtiException
2
+ from .abstract import MessageValidatorAbstract
3
+
4
+
5
+ class SubmissionReviewLaunchValidator(MessageValidatorAbstract):
6
+ """Validates the body of a LTI submission review launch.
7
+
8
+ The launch must include a for_user claim specifying the user
9
+ who's submission is being reviewed, as well as the line item
10
+ for the reviewed submission.
11
+ """
12
+
13
+ def validate(self, jwt_body) -> bool:
14
+ self.run_common_validators(jwt_body)
15
+
16
+ if "https://purl.imsglobal.org/spec/lti-ags/claim/endpoint" not in jwt_body:
17
+ raise LtiException(
18
+ "Grade services must be included in a LtiSubmissionReviewRequest"
19
+ )
20
+
21
+ ags_endpoint_claim = jwt_body[
22
+ "https://purl.imsglobal.org/spec/lti-ags/claim/endpoint"
23
+ ]
24
+ if "lineitem" not in ags_endpoint_claim:
25
+ raise LtiException(
26
+ "A LtiSubmissionReviewRequest must specify the lineitem it was launched for"
27
+ )
28
+
29
+ for_user_claim = jwt_body.get(
30
+ "https://purl.imsglobal.org/spec/lti/claim/for_user"
31
+ )
32
+ if for_user_claim is None:
33
+ raise LtiException(
34
+ "For user claim must be included in a LtiSubmissionReviewRequest"
35
+ )
36
+ if "user_id" not in for_user_claim:
37
+ raise LtiException("For user claim must include user_id")
38
+
39
+ return True
40
+
41
+ def can_validate(self, jwt_body) -> bool:
42
+ return (
43
+ jwt_body.get("https://purl.imsglobal.org/spec/lti/claim/message_type")
44
+ == "LtiSubmissionReviewRequest"
45
+ )
@@ -0,0 +1,97 @@
1
+ import typing as t
2
+ import typing_extensions as te
3
+ from .utils import add_param_to_url
4
+ from .service_connector import ServiceConnector
5
+
6
+ TNamesAndRolesData = te.TypedDict(
7
+ "TNamesAndRolesData",
8
+ {
9
+ "context_memberships_url": str,
10
+ },
11
+ total=False,
12
+ )
13
+
14
+ TMember = te.TypedDict(
15
+ "TMember",
16
+ {
17
+ "name": str,
18
+ "status": te.Literal["Active", "Inactive", "Deleted"],
19
+ "picture": str,
20
+ "given_name": str,
21
+ "family_name": str,
22
+ "middle_name": str,
23
+ "email": str,
24
+ "user_id": str,
25
+ "lis_person_sourcedid": str,
26
+ "roles": t.List[str],
27
+ "message": t.Union[t.List[t.Dict[str, object]], t.Dict[str, object]],
28
+ "lti11_legacy_user_id": t.Optional[str],
29
+ },
30
+ total=False,
31
+ )
32
+
33
+
34
+ class NamesRolesProvisioningService:
35
+ _service_connector: ServiceConnector
36
+ _service_data: TNamesAndRolesData
37
+
38
+ def __init__(
39
+ self, service_connector: ServiceConnector, service_data: TNamesAndRolesData
40
+ ):
41
+ self._service_connector = service_connector
42
+ self._service_data = service_data
43
+
44
+ def get_nrps_data(self, members_url: t.Optional[str] = None):
45
+ if not members_url:
46
+ members_url = self._service_data["context_memberships_url"]
47
+
48
+ data = self._service_connector.make_service_request(
49
+ [
50
+ "https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly"
51
+ ],
52
+ members_url,
53
+ accept="application/vnd.ims.lti-nrps.v2.membershipcontainer+json",
54
+ )
55
+ return data
56
+
57
+ def get_members_page(
58
+ self, members_url: t.Optional[str] = None
59
+ ) -> t.Tuple[t.List[TMember], t.Optional[str]]:
60
+ """
61
+ Get one page with the users.
62
+
63
+ :param members_url: LTI platform's URL (optional)
64
+ :return: tuple in format: (list with users, next page url)
65
+ """
66
+ data = self.get_nrps_data(members_url=members_url)
67
+ data_body = t.cast(t.Any, data.get("body", {}))
68
+ return data_body.get("members", []), data["next_page_url"]
69
+
70
+ def get_members(self, resource_link_id: t.Optional[str] = None) -> t.List[TMember]:
71
+ """
72
+ Get list with all users.
73
+
74
+ :param resource_link_id: resource link id (optional)
75
+ :return: list
76
+ """
77
+ members_res_lst: t.List[TMember] = []
78
+ members_url: t.Optional[str] = self._service_data["context_memberships_url"]
79
+
80
+ if members_url and resource_link_id:
81
+ members_url = add_param_to_url(members_url, "rlid", resource_link_id)
82
+
83
+ while members_url:
84
+ members, members_url = self.get_members_page(members_url)
85
+ members_res_lst.extend(members)
86
+
87
+ return members_res_lst
88
+
89
+ def get_context(self):
90
+ """
91
+ Get context data.
92
+
93
+ :return: dict
94
+ """
95
+ data = self.get_nrps_data()
96
+ data_body = t.cast(t.Any, data.get("body", {}))
97
+ return data_body.get("context", {})
@@ -0,0 +1,275 @@
1
+ import typing as t
2
+ import uuid
3
+ from abc import ABCMeta, abstractmethod
4
+ from urllib.parse import urlencode
5
+
6
+ from .actions import Action
7
+ from .cookie import CookieService
8
+ from .cookies_allowed_check import CookiesAllowedCheckPage
9
+ from .exception import OIDCException
10
+ from .launch_data_storage.base import LaunchDataStorage
11
+ from .session import SessionService
12
+ from .registration import Registration
13
+ from .redirect import Redirect
14
+ from .request import Request
15
+ from .tool_config import ToolConfAbstract
16
+
17
+
18
+ RED = t.TypeVar("RED")
19
+ REQ = t.TypeVar("REQ", bound=Request)
20
+ TCONF = t.TypeVar("TCONF", bound=ToolConfAbstract)
21
+ SES = t.TypeVar("SES", bound=SessionService)
22
+ COOK = t.TypeVar("COOK", bound=CookieService)
23
+
24
+
25
+ class OIDCLogin(t.Generic[REQ, TCONF, SES, COOK, RED]):
26
+ __metaclass__ = ABCMeta
27
+ _request: REQ
28
+ _tool_config: TCONF
29
+ _session_service: SES
30
+ _cookie_service: COOK
31
+ _launch_data_storage: t.Optional[LaunchDataStorage[t.Any]] = None
32
+ _registration: Registration
33
+
34
+ _cookies_check: bool = False
35
+ _cookies_check_loading_text: str = "Loading..."
36
+ _cookies_unavailable_msg_main_text: str = (
37
+ "Your browser prohibits to save cookies in the iframes."
38
+ )
39
+ _cookies_unavailable_msg_click_text: str = (
40
+ "Click here to open content in the new tab."
41
+ )
42
+ _state_params: t.Dict[str, object] = {}
43
+
44
+ def __init__(
45
+ self,
46
+ request: REQ,
47
+ tool_config: TCONF,
48
+ session_service: SES,
49
+ cookie_service: COOK,
50
+ launch_data_storage: t.Optional[LaunchDataStorage[t.Any]] = None,
51
+ ):
52
+ self._request = request
53
+ self._tool_config = tool_config
54
+ self._session_service = session_service
55
+ self._cookie_service = cookie_service
56
+ self._launch_data_storage = launch_data_storage
57
+
58
+ @abstractmethod
59
+ def get_redirect(self, url: str) -> Redirect[RED]:
60
+ raise NotImplementedError
61
+
62
+ def get_response(self, html: str) -> RED: # pylint: disable=unused-argument
63
+ return "" # type: ignore
64
+
65
+ def get_iss(self) -> t.Optional[str]:
66
+ if self._registration:
67
+ return self._registration.get_issuer()
68
+ return None
69
+
70
+ def get_client_id(self) -> t.Optional[str]:
71
+ if self._registration:
72
+ return self._registration.get_client_id()
73
+ return None
74
+
75
+ def _get_request_param(self, key: str) -> str:
76
+ return self._request.get_param(key)
77
+
78
+ def _get_uuid(self) -> str:
79
+ return str(uuid.uuid4())
80
+
81
+ def _generate_nonce(self) -> str:
82
+ return uuid.uuid4().hex + uuid.uuid1().hex
83
+
84
+ def _is_new_window_request(self) -> bool:
85
+ lti_new_window = self._get_request_param("lti1p3_new_window")
86
+ return bool(lti_new_window)
87
+
88
+ def _prepare_redirect_url(self, launch_url: str) -> str:
89
+ if not launch_url:
90
+ raise OIDCException("No launch URL configured")
91
+
92
+ if self._launch_data_storage:
93
+ self.set_launch_data_storage(self._launch_data_storage)
94
+
95
+ # validate request
96
+ self._registration = self.validate_oidc_login()
97
+
98
+ # build OIDC Auth Response
99
+
100
+ # generate state
101
+ # set cookie (short lived)
102
+ state = "state-" + self._get_uuid()
103
+ self._cookie_service.set_cookie(state, state, 5 * 60) # 5 min
104
+
105
+ # generate nonce
106
+ nonce = self._generate_nonce()
107
+ self._session_service.save_nonce(nonce)
108
+ if self._state_params:
109
+ self._session_service.save_state_params(state, self._state_params)
110
+
111
+ # build Response
112
+ client_id = self._registration.get_client_id() # Registered client id
113
+ assert client_id is not None, "Client id should not be None"
114
+ auth_login_url = self._registration.get_auth_login_url()
115
+ assert auth_login_url is not None, "Auth login url should not be None"
116
+
117
+ auth_params = {
118
+ "scope": "openid", # OIDC Scope
119
+ "response_type": "id_token", # OIDC response is always an id token
120
+ "response_mode": "form_post", # OIDC response is always a form post
121
+ "prompt": "none", # Don't prompt user on redirect
122
+ "client_id": client_id, # Registered client id
123
+ "redirect_uri": launch_url, # URL to return to after login
124
+ "state": state, # State to identify browser session
125
+ "nonce": nonce, # Prevent replay attacks
126
+ "login_hint": self._get_request_param(
127
+ "login_hint"
128
+ ), # Login hint to identify platform session
129
+ }
130
+
131
+ # pass back LTI message hint if we have it
132
+ lti_message_hint = self._get_request_param("lti_message_hint")
133
+ if lti_message_hint:
134
+ # LTI message hint to identify LTI context within the platform
135
+ auth_params["lti_message_hint"] = lti_message_hint
136
+
137
+ auth_login_return_url = auth_login_url + "?" + urlencode(auth_params)
138
+ return auth_login_return_url
139
+
140
+ def _prepare_redirect(self, launch_url: str) -> Redirect[RED]:
141
+ auth_login_return_url = self._prepare_redirect_url(launch_url)
142
+ return self.get_redirect(auth_login_return_url)
143
+
144
+ def redirect(self, launch_url: str, js_redirect: bool = False) -> RED:
145
+ """
146
+ Calculate the redirect location to return to based on an OIDC third party initiated login request.
147
+
148
+ :param launch_url: URL to redirect back to after the OIDC login.
149
+ This URL must match exactly a URL white listed in the platform.
150
+ :param js_redirect: Redirect through JS
151
+ :return: Returns a redirect object containing the fully formed OIDC login URL.
152
+ """
153
+ if self._cookies_check:
154
+ if not self._is_new_window_request():
155
+ html = self.get_cookies_allowed_js_check()
156
+ return self.get_response(html)
157
+
158
+ redirect_obj = self._prepare_redirect(launch_url)
159
+ if js_redirect:
160
+ return redirect_obj.do_js_redirect()
161
+ return redirect_obj.do_redirect()
162
+
163
+ def get_redirect_object(self, launch_url: str) -> Redirect[RED]:
164
+ return self._prepare_redirect(launch_url)
165
+
166
+ def validate_oidc_login(self) -> Registration:
167
+ # validate Issuer
168
+ iss = self._get_request_param("iss")
169
+ if not iss:
170
+ raise OIDCException("Could not find issuer")
171
+
172
+ # validate login hint
173
+ login_hint = self._get_request_param("login_hint")
174
+ if not login_hint:
175
+ raise OIDCException("Could not find login hint")
176
+
177
+ client_id = self._get_request_param("client_id")
178
+
179
+ # fetch registration details
180
+ if self._tool_config.check_iss_has_one_client(iss):
181
+ registration = self._tool_config.find_registration(
182
+ iss, action=Action.OIDC_LOGIN, request=self._request
183
+ )
184
+ else:
185
+ registration = self._tool_config.find_registration_by_params(
186
+ iss, client_id, action=Action.OIDC_LOGIN, request=self._request
187
+ )
188
+
189
+ # check we got something
190
+ if not registration:
191
+ raise OIDCException("Could not find registration details")
192
+
193
+ return registration
194
+
195
+ def pass_params_to_launch(self, params: t.Dict[str, object]) -> "OIDCLogin":
196
+ """
197
+ Ability to pass custom params from oidc login to launch.
198
+ """
199
+ self._state_params = params
200
+ return self
201
+
202
+ def enable_check_cookies(
203
+ self,
204
+ main_msg: t.Optional[str] = None,
205
+ click_msg: t.Optional[str] = None,
206
+ loading_msg: t.Optional[str] = None,
207
+ **kwargs
208
+ ) -> "OIDCLogin":
209
+ # pylint: disable=unused-argument
210
+ self._cookies_check = True
211
+ if main_msg:
212
+ self._cookies_unavailable_msg_main_text = main_msg
213
+ if click_msg:
214
+ self._cookies_unavailable_msg_click_text = click_msg
215
+ if loading_msg:
216
+ self._cookies_check_loading_text = loading_msg
217
+ return self
218
+
219
+ def disable_check_cookies(self) -> "OIDCLogin":
220
+ self._cookies_check = False
221
+ return self
222
+
223
+ def get_additional_login_params(self) -> t.List[str]:
224
+ """
225
+ You may add additional custom params in your own OIDCLogin class
226
+ :return: list
227
+ """
228
+ return []
229
+
230
+ def get_cookies_allowed_js_check(self) -> str:
231
+ protocol = "https" if self._request.is_secure() else "http"
232
+ params_lst = [
233
+ "iss",
234
+ "login_hint",
235
+ "target_link_uri",
236
+ "lti_message_hint",
237
+ "lti_deployment_id",
238
+ "client_id",
239
+ ]
240
+ additional_login_params = self.get_additional_login_params()
241
+ params_lst.extend(additional_login_params)
242
+
243
+ params = {"lti1p3_new_window": "1"}
244
+ for param_key in params_lst:
245
+ param_value = self._get_request_param(param_key)
246
+ if param_value:
247
+ params[param_key] = param_value
248
+
249
+ page = CookiesAllowedCheckPage(
250
+ params,
251
+ protocol,
252
+ self._cookies_unavailable_msg_main_text,
253
+ self._cookies_unavailable_msg_click_text,
254
+ self._cookies_check_loading_text,
255
+ )
256
+
257
+ return page.get_html()
258
+
259
+ def set_launch_data_storage(
260
+ self, data_storage: LaunchDataStorage[t.Any]
261
+ ) -> "OIDCLogin":
262
+ data_storage.set_request(self._request)
263
+ session_cookie_name = data_storage.get_session_cookie_name()
264
+ if session_cookie_name:
265
+ session_id = self._cookie_service.get_cookie(session_cookie_name)
266
+ if not session_id:
267
+ session_id = self._get_uuid()
268
+ self._cookie_service.set_cookie(session_cookie_name, session_id, None)
269
+ data_storage.set_session_id(session_id)
270
+ self._session_service.set_data_storage(data_storage)
271
+ return self
272
+
273
+ def set_launch_data_lifetime(self, time_sec: int) -> "OIDCLogin":
274
+ self._session_service.set_launch_data_lifetime(time_sec)
275
+ return self
File without changes
@@ -0,0 +1,24 @@
1
+ import typing as t
2
+ from abc import ABCMeta, abstractmethod
3
+
4
+ T = t.TypeVar("T")
5
+
6
+
7
+ class Redirect(t.Generic[T]):
8
+ __metaclass__ = ABCMeta
9
+
10
+ @abstractmethod
11
+ def do_redirect(self) -> T:
12
+ raise NotImplementedError
13
+
14
+ @abstractmethod
15
+ def do_js_redirect(self) -> T:
16
+ raise NotImplementedError
17
+
18
+ @abstractmethod
19
+ def set_redirect_url(self, location: str):
20
+ raise NotImplementedError
21
+
22
+ @abstractmethod
23
+ def get_redirect_url(self) -> str:
24
+ raise NotImplementedError