diff --git a/composer.json b/composer.json index 9ccdb1a..20ea33a 100644 --- a/composer.json +++ b/composer.json @@ -8,8 +8,15 @@ "issues": "https://drupal.org/project/issues/lti_tool_provider", "source": "https://git.drupalcode.org/project/lti_tool_provider" }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/IMSGlobal/lti-1-3-php-library" + } + ], "require": { "php": "^7.3", - "ext-oauth": "*" + "ext-oauth": "*", + "imsglobal/lti-1p3-tool": "dev-master" } } diff --git a/config/install/lti_tool_provider.settings.yml b/config/install/lti_tool_provider.settings.yml index 02175c0..e1d7a43 100755 --- a/config/install/lti_tool_provider.settings.yml +++ b/config/install/lti_tool_provider.settings.yml @@ -1,7 +1,7 @@ langcode: en iframe: false destination: '/' -lti_launch: +lti_launch_v1p0: - user_id - lis_person_sourcedid - roles @@ -34,7 +34,44 @@ lti_launch: - launch_presentation_return_url - consumer_id - consumer_label -lti_roles: +lti_launch_v1p3: + - 'nonce' + - 'iat' + - 'exp' + - 'iss' + - 'aud' + - 'https://purl.imsglobal.org/spec/lti/claim/deployment_id' + - 'https://purl.imsglobal.org/spec/lti/claim/target_link_uri' + - 'sub' + - 'https://purl.imsglobal.org/spec/lti/claim/lis - person_sourcedid' + - 'https://purl.imsglobal.org/spec/lti/claim/lis - course_section_sourcedid' + - 'https://purl.imsglobal.org/spec/lti/claim/context - id' + - 'https://purl.imsglobal.org/spec/lti/claim/context - label' + - 'https://purl.imsglobal.org/spec/lti/claim/context - title' + - 'https://purl.imsglobal.org/spec/lti/claim/context - type - 0' + - 'https://purl.imsglobal.org/spec/lti/claim/resource_link - id' + - 'https://purl.imsglobal.org/spec/lti/claim/resource_link - title' + - 'https://purl.imsglobal.org/spec/lti/claim/resource_link - description' + - 'https://purl.imsglobal.org/spec/lti-bos/claim/basicoutcomesservice - lis_outcome_service_url' + - 'given_name' + - 'family_name' + - 'name' + - 'email' + - 'https://purl.imsglobal.org/spec/lti/claim/ext - user_username' + - 'https://purl.imsglobal.org/spec/lti/claim/ext - lms' + - 'https://purl.imsglobal.org/spec/lti/claim/launch_presentation - locale' + - 'https://purl.imsglobal.org/spec/lti/claim/launch_presentation - document_target' + - 'https://purl.imsglobal.org/spec/lti/claim/launch_presentation - return_url' + - 'https://purl.imsglobal.org/spec/lti/claim/tool_platform - family_code' + - 'https://purl.imsglobal.org/spec/lti/claim/tool_platform - version' + - 'https://purl.imsglobal.org/spec/lti/claim/tool_platform - guid' + - 'https://purl.imsglobal.org/spec/lti/claim/tool_platform - name' + - 'https://purl.imsglobal.org/spec/lti/claim/tool_platform - description' + - 'https://purl.imsglobal.org/spec/lti/claim/version' + - 'https://purl.imsglobal.org/spec/lti/claim/message_type' + - 'consumer_id' + - 'consumer_label' +lti_roles_v1p0: - 'urn:lti:sysrole:ims/lis/SysAdmin' - 'urn:lti:sysrole:ims/lis/SysSupport' - 'urn:lti:sysrole:ims/lis/Creator' @@ -108,3 +145,79 @@ lti_roles: - 'urn:lti:role:ims/lis/TeachingAssistant/TeachingAssistantTemplate' - 'urn:lti:role:ims/lis/TeachingAssistant/TeachingAssistantGroup' - 'urn:lti:role:ims/lis/TeachingAssistant/Grader' +lti_roles_v1p3: + - 'http://purl.imsglobal.org/vocab/lis/v2/system/person#Administrator' + - 'http://purl.imsglobal.org/vocab/lis/v2/system/person#None' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Faculty' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Guest' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#None' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Other' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Staff' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Student' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Alumni' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Instructor' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Learner' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Member' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Mentor' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Observer' + - 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#ProspectiveStudent' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership#Administrator' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#Administrator' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#Developer' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#ExternalDeveloper' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#ExternalSupport' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#ExternalSystemAdministrator' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#Support' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#SystemAdministrator' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership#ContentDeveloper' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#ContentDeveloper' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#ContentExpert' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#ExternalContentExpert' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#Librarian' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#ExternalInstructor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#Grader' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#GuestInstructor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#Lecturer' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#PrimaryInstructor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#SecondaryInstructor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistant' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantGroup' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantOffering' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantSection' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantSectionAssociation' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantTemplate' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#ExternalLearner' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#GuestLearner' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#Instructor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#Learner' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#NonCreditLearner' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership#Mentor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Advisor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Auditor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalAdvisor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalAuditor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalLearningFacilitator' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalMentor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalReviewer' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalTutor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#LearningFacilitator' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Mentor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Reviewer' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Tutor' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership#Manager' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#AreaManager' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#CourseCoordinator' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#ExternalObserver' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#Manager' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#Observer' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership#Member' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Member#Member' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership#Officer' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Chair' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Communications' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Secretary' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Treasurer' + - 'http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Vice-Chair' \ No newline at end of file diff --git a/lti_tool_provider.info.yml b/lti_tool_provider.info.yml index 74e6c99..af9c5b6 100644 --- a/lti_tool_provider.info.yml +++ b/lti_tool_provider.info.yml @@ -5,3 +5,5 @@ package: LTI Tool Provider core: 8.x core_version_requirement: ^8 || ^9 configure: lti_tool_provider.admin +dependencies: + - options \ No newline at end of file diff --git a/lti_tool_provider.install b/lti_tool_provider.install index 2f8ecef..5bcf739 100644 --- a/lti_tool_provider.install +++ b/lti_tool_provider.install @@ -1,5 +1,8 @@ getEditable('lti_tool_provider.settings'); - $lti_roles = $config->get('lti_roles'); + $lti_roles = $config->get('lti_roles_v1p0'); $lti_roles = array_merge( [ 'urn:lti:sysrole:ims/lis/SysAdmin', @@ -71,6 +74,428 @@ function lti_tool_provider_update_8101() ], $lti_roles ); - $config->set('lti_roles', $lti_roles); + $config->set('lti_roles_v1p0', $lti_roles); + $config->save(true); +} + +/** + * Implements hook_update_N(). + */ +function lti_tool_provider_update_8102() { + $field_lti_version_definition = BaseFieldDefinition::create('string') + ->setLabel(t('LTI version')) + ->setDescription(t('LTI version of the Consumer entity.')) + ->setRequired(true) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 1, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 1, + ] + ) + ->setDisplayConfigurable('form', true) + ->setDisplayConfigurable('view', true); + + $field_platform_id_definition = BaseFieldDefinition::create('string') + ->setLabel(t('Issuer (Platform Id)')) + ->setDescription(t('The issuer of Consumer entity.')) + ->setRequired(true) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 2, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 2, + ] + ) + ->setDisplayConfigurable('form', true) + ->setDisplayConfigurable('view', true); + + $field_client_id_definition = BaseFieldDefinition::create('string') + ->setLabel(t('Client Id')) + ->setDescription(t('The Client Id of the Consumer entity.')) + ->setRequired(true) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 3, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 3, + ] + ) + ->setDisplayConfigurable('form', true) + ->setDisplayConfigurable('view', true); + + $field_deployment_id_definition = BaseFieldDefinition::create('string') + ->setLabel(t('Deployment Id')) + ->setDescription(t('The Deployment Id of the Consumer entity.')) + ->setRequired(true) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 4, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 4, + ] + ) + ->setDisplayConfigurable('form', true) + ->setDisplayConfigurable('view', true); + + $field_key_set_url_definition = BaseFieldDefinition::create('string') + ->setLabel(t('Public keyset URL')) + ->setDescription(t('Public keyset URL of the Consumer entity.')) + ->setRequired(true) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 5, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 5, + ] + ) + ->setDisplayConfigurable('form', true) + ->setDisplayConfigurable('view', true); + + $field_consumer_kid_definition = BaseFieldDefinition::create('string') + ->setLabel(t('KID')) + ->setDescription(t('The KID of the Consumer entity.')) + ->setRequired(false) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 6, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 6, + ] + ) + ->setDisplayConfigurable('form', true) + ->setDisplayConfigurable('view', true); + + $field_auth_token_url_definition = BaseFieldDefinition::create('string') + ->setLabel(t('Access token URL')) + ->setDescription(t('Access token URL of the Consumer entity.')) + ->setRequired(false) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 7, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 7, + ] + ) + ->setDisplayConfigurable('form', true) + ->setDisplayConfigurable('view', true); + + $field_auth_login_url_definition = BaseFieldDefinition::create('string') + ->setLabel(t('Authentication request URL')) + ->setDescription(t('Authentication request URL of the Consumer entity.')) + ->setRequired(false) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 8, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 8, + ] + ) + ->setDisplayConfigurable('form', true) + ->setDisplayConfigurable('view', true); + + $field_private_key_definition = BaseFieldDefinition::create('string') + ->setLabel(t('Private key')) + ->setDescription(t('Private key of the Consumer entity.')) + ->setRequired(true) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 9, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 9, + ] + ) + ->setDisplayConfigurable('form', true) + ->setDisplayConfigurable('view', true); + + $update_manager = \Drupal::entityDefinitionUpdateManager(); + $update_manager->installFieldStorageDefinition('lti_version', 'lti_tool_provider_consumer', 'lti_tool_provider_consumer' , $field_lti_version_definition); + $update_manager->installFieldStorageDefinition('platform_id', 'lti_tool_provider_consumer', 'lti_tool_provider_consumer' , $field_platform_id_definition); + $update_manager->installFieldStorageDefinition('client_id', 'lti_tool_provider_consumer', 'lti_tool_provider_consumer' , $field_client_id_definition); + $update_manager->installFieldStorageDefinition('deployment_id', 'lti_tool_provider_consumer', 'lti_tool_provider_consumer' , $field_deployment_id_definition); + $update_manager->installFieldStorageDefinition('key_set_url', 'lti_tool_provider_consumer', 'lti_tool_provider_consumer' , $field_key_set_url_definition); + $update_manager->installFieldStorageDefinition('consumer_kid', 'lti_tool_provider_consumer', 'lti_tool_provider_consumer' , $field_consumer_kid_definition); + $update_manager->installFieldStorageDefinition('auth_token_url', 'lti_tool_provider_consumer', 'lti_tool_provider_consumer' , $field_auth_token_url_definition); + $update_manager->installFieldStorageDefinition('auth_login_url', 'lti_tool_provider_consumer', 'lti_tool_provider_consumer' , $field_auth_login_url_definition); + $update_manager->installFieldStorageDefinition('private_key', 'lti_tool_provider_consumer', 'lti_tool_provider_consumer' , $field_private_key_definition); +} + +/** + * Implements hook_update_N(). + */ +function lti_tool_provider_update_8103() { + $field_client_id_definition = BaseFieldDefinition::create('string') + ->setLabel(t('Consumer Client Id')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); + + $update_manager = \Drupal::entityDefinitionUpdateManager(); + $update_manager->installFieldStorageDefinition('client_id', 'lti_tool_provider_nonce', 'lti_tool_provider_nonce' , $field_client_id_definition); +} + +/** + * Replace all LTI launch in config. + */ +function lti_tool_provider_update_8104() +{ + $config_factory = Drupal::configFactory(); + $config = $config_factory->getEditable('lti_tool_provider.settings'); + $lti_launch = [ + 'nonce', + 'iat', + 'exp', + 'iss', + 'aud', + 'https://purl.imsglobal.org/spec/lti/claim/deployment_id', + 'https://purl.imsglobal.org/spec/lti/claim/target_link_uri', + 'sub', + 'https://purl.imsglobal.org/spec/lti/claim/lis - person_sourcedid', + 'https://purl.imsglobal.org/spec/lti/claim/lis - course_section_sourcedid', + 'https://purl.imsglobal.org/spec/lti/claim/context - id', + 'https://purl.imsglobal.org/spec/lti/claim/context - label', + 'https://purl.imsglobal.org/spec/lti/claim/context - title', + 'https://purl.imsglobal.org/spec/lti/claim/context - type - 0', + 'https://purl.imsglobal.org/spec/lti/claim/resource_link - id', + 'https://purl.imsglobal.org/spec/lti/claim/resource_link - title', + 'https://purl.imsglobal.org/spec/lti/claim/resource_link - description', + 'https://purl.imsglobal.org/spec/lti-bos/claim/basicoutcomesservice - lis_outcome_service_url', + 'given_name', + 'family_name', + 'name', + 'email', + 'https://purl.imsglobal.org/spec/lti/claim/ext - user_username', + 'https://purl.imsglobal.org/spec/lti/claim/ext - lms', + 'https://purl.imsglobal.org/spec/lti/claim/launch_presentation - locale', + 'https://purl.imsglobal.org/spec/lti/claim/launch_presentation - document_target', + 'https://purl.imsglobal.org/spec/lti/claim/launch_presentation - return_url', + 'https://purl.imsglobal.org/spec/lti/claim/tool_platform - family_code', + 'https://purl.imsglobal.org/spec/lti/claim/tool_platform - version', + 'https://purl.imsglobal.org/spec/lti/claim/tool_platform - guid', + 'https://purl.imsglobal.org/spec/lti/claim/tool_platform - name', + 'https://purl.imsglobal.org/spec/lti/claim/tool_platform - description', + 'https://purl.imsglobal.org/spec/lti/claim/version', + 'https://purl.imsglobal.org/spec/lti/claim/message_type', + 'consumer_id', + 'consumer_label' + ]; + $config->set('lti_launch_v1p3', $lti_launch); + $config->save(true); +} + +/** + * Replace all LTI roles in config. + */ +function lti_tool_provider_update_8105() +{ + $config_factory = Drupal::configFactory(); + $config = $config_factory->getEditable('lti_tool_provider.settings'); + $lti_roles = [ + 'http://purl.imsglobal.org/vocab/lis/v2/system/person#Administrator', + 'http://purl.imsglobal.org/vocab/lis/v2/system/person#None', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Faculty', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Guest', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#None', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Other', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Staff', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Student', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Alumni', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Instructor', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Learner', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Member', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Mentor', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Observer', + 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#ProspectiveStudent', + 'http://purl.imsglobal.org/vocab/lis/v2/membership#Administrator', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#Administrator', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#Developer', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#ExternalDeveloper', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#ExternalSupport', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#ExternalSystemAdministrator', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#Support', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#SystemAdministrator', + 'http://purl.imsglobal.org/vocab/lis/v2/membership#ContentDeveloper', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#ContentDeveloper', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#ContentExpert', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#ExternalContentExpert', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#Librarian', + 'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#ExternalInstructor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#Grader', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#GuestInstructor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#Lecturer', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#PrimaryInstructor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#SecondaryInstructor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistant', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantGroup', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantOffering', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantSection', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantSectionAssociation', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantTemplate', + 'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#ExternalLearner', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#GuestLearner', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#Instructor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#Learner', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#NonCreditLearner', + 'http://purl.imsglobal.org/vocab/lis/v2/membership#Mentor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Advisor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Auditor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalAdvisor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalAuditor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalLearningFacilitator', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalMentor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalReviewer', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalTutor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#LearningFacilitator', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Mentor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Reviewer', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Tutor', + 'http://purl.imsglobal.org/vocab/lis/v2/membership#Manager', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#AreaManager', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#CourseCoordinator', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#ExternalObserver', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#Manager', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#Observer', + 'http://purl.imsglobal.org/vocab/lis/v2/membership#Member', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Member#Member', + 'http://purl.imsglobal.org/vocab/lis/v2/membership#Officer', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Chair', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Communications', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Secretary', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Treasurer', + 'http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Vice-Chair' + ]; + $config->set('lti_roles_v1p3', $lti_roles); $config->save(true); } diff --git a/lti_tool_provider.module b/lti_tool_provider.module index 635ba97..8345fbb 100644 --- a/lti_tool_provider.module +++ b/lti_tool_provider.module @@ -15,38 +15,42 @@ const LTI_TOOL_PROVIDER_NONCE_INTERVAL = (5 * 60); */ const LTI_TOOL_PROVIDER_NONCE_EXPIRY = (1.5 * 60 * 60); +/** + * LTI claim base url from context. + */ +const LTI_CLAIM_BASE_URL = 'https://purl.imsglobal.org/spec/lti/claim/'; + /** * Implements hook_help(). + * * @param $route_name * @return string|null */ -function lti_tool_provider_help($route_name): ?string -{ - switch ($route_name) { - case 'help.page.lti_tool_provider': - return '

' . t( - 'The LTI tool provider module provides an oauth based authentication provider for Drupal, as well as configuration options for consumers and user provisioning.' - ) . '

'; - } +function lti_tool_provider_help($route_name): ?string { + switch ($route_name) { + case 'help.page.lti_tool_provider': + return '

' . t( + 'The LTI tool provider module provides an authentication provider for Drupal, as well as configuration options for consumers and user provisioning.' + ) . '

'; + } - return null; + return NULL; } /** * Implements hook_cron(). */ -function lti_tool_provider_cron() -{ - $expires = time() - LTI_TOOL_PROVIDER_NONCE_EXPIRY; - try { - $nonceStorage = Drupal::entityTypeManager()->getStorage('lti_tool_provider_nonce'); - $ids = $nonceStorage->getQuery()->condition('timestamp', $expires, '<')->execute(); - $entities = $nonceStorage->loadMultiple($ids); - $nonceStorage->delete($entities); - } - catch (Exception $e) { - Drupal::logger('lti_tool_provider')->error($e->getMessage()); - } +function lti_tool_provider_cron() { + $expires = time() - LTI_TOOL_PROVIDER_NONCE_EXPIRY; + try { + $nonceStorage = Drupal::entityTypeManager()->getStorage('lti_tool_provider_nonce'); + $ids = $nonceStorage->getQuery()->condition('timestamp', $expires, '<')->execute(); + $entities = $nonceStorage->loadMultiple($ids); + $nonceStorage->delete($entities); + } + catch (Exception $e) { + Drupal::logger('lti_tool_provider')->error($e->getMessage()); + } } /** @@ -58,23 +62,76 @@ function lti_tool_provider_cron() * @return array * An array of roles */ -function parse_roles($roles): array -{ - $parsedRoles = []; +function parse_roles($roles): array { + $parsedRoles = []; + + if (!is_array($roles)) { + $roles = explode(',', $roles); + } - if (!is_array($roles)) { - $roles = explode(',', $roles); + foreach ($roles as $role) { + $role = trim($role); + if (!empty($role)) { + if (substr($role, 0, 4) !== 'urn:') { + $role = 'urn:lti:role:ims/lis/' . $role; + } + $parsedRoles[] = $role; } + } + + return $parsedRoles; +} - foreach ($roles as $role) { - $role = trim($role); - if (!empty($role)) { - if (substr($role, 0, 4) !== 'urn:') { - $role = 'urn:lti:role:ims/lis/' . $role; - } - $parsedRoles[] = $role; - } +/** + * Utility function to get a context data by property name. + * + * @param array $context + * Context array. + * @param string $lti_attribute + * String contain properties separated - . + * @return mixed|null + * Return value by property. + */ +function get_data_from_context_by_lti_attribute(array $context, string $lti_attribute) { + $attributes_tree = explode(' - ', $lti_attribute); + + try { + foreach ($attributes_tree as $attribute) { + if (isset($context)) { + $context = $context[$attribute]; + } } - return $parsedRoles; + return $context; + } + catch (Exception $e) { + return NULL; + } +} + +/** + * Utility function to get a LTI version from consumer by launch data. + * + * @param $context + * Launch data. + * + * @return mixed + * LTI version string. + */ +function get_lti_version_by_launch_data($context) { + $consumer_params = []; + + if (isset($context['aud'])) { + $consumer_params['client_id'] = $context['aud']; + } + elseif (isset($context['oauth_consumer_key'])) { + $consumer_params['consumer_key'] = $context['oauth_consumer_key']; + } + + $consumers = \Drupal::entityTypeManager() + ->getStorage('lti_tool_provider_consumer') + ->loadByProperties($consumer_params); + $consumer = reset($consumers); + + return $consumer->get('lti_version')->getValue()[0]['value']; } diff --git a/lti_tool_provider.routing.yml b/lti_tool_provider.routing.yml index cf850fe..ef206e4 100644 --- a/lti_tool_provider.routing.yml +++ b/lti_tool_provider.routing.yml @@ -7,12 +7,36 @@ lti_tool_provider.lti: - lti_auth requirements: _custom_access: '\Drupal\lti_tool_provider\Controller\LTIToolProviderController::access' +lti_tool_provider.lti.launch: + path: /lti/launch + defaults: + _controller: '\Drupal\lti_tool_provider\Controller\LTIToolProviderController::ltiLaunch' + options: + _auth: + - lti_auth + requirements: + _custom_access: '\Drupal\lti_tool_provider\Controller\LTIToolProviderController::access' lti_tool_provider.lti.return: path: /lti/return defaults: _controller: '\Drupal\lti_tool_provider\Controller\LTIToolProviderController::ltiReturn' + options: + _auth: + - lti_auth requirements: _custom_access: '\Drupal\lti_tool_provider\Controller\LTIToolProviderController::access' +lti_tool_provider.lti.login: + path: /lti/login + defaults: + _controller: '\Drupal\lti_tool_provider\Controller\LTIToolProviderController::ltiLogin' + requirements: + _access: 'TRUE' +lti_tool_provider.lti.jwks: + path: /lti/jwks + defaults: + _controller: '\Drupal\lti_tool_provider\Controller\LTIToolProviderController::ltiJwks' + requirements: + _access: 'TRUE' lti_tool_provider.admin: path: admin/config/lti-tool-provider defaults: diff --git a/lti_tool_provider.services.yml b/lti_tool_provider.services.yml index 0ca9812..65db57e 100644 --- a/lti_tool_provider.services.yml +++ b/lti_tool_provider.services.yml @@ -1,15 +1,23 @@ services: authentication.lti_tool_provider: class: Drupal\lti_tool_provider\Authentication\Provider\LTIToolProvider - arguments: - - '@config.factory' - - '@entity_type.manager' - - '@logger.factory' - - '@event_dispatcher' + arguments: [ + '@config.factory', + '@entity_type.manager', + '@logger.factory', + '@event_dispatcher', + '@lti_tool_provider.lti_service' + ] tags: - name: authentication_provider provider_id: lti_auth priority: 100 + lti_tool_provider.lti_service: + class: Drupal\lti_tool_provider\Services\LTIService + arguments: ['@lti_tool_provider.lti_database'] + lti_tool_provider.lti_database: + class: Drupal\lti_tool_provider\Services\LTIDatabase + arguments: [] lti_tool_provider.xframe.event_subscriber: class: Drupal\lti_tool_provider\EventSubscriber\RemoveXFrameOptionsSubscriber tags: diff --git a/modules/lti_tool_provider_attributes/lti_tool_provider_attributes.links.menu.yml b/modules/lti_tool_provider_attributes/lti_tool_provider_attributes.links.menu.yml index 494962c..e5d7f2a 100644 --- a/modules/lti_tool_provider_attributes/lti_tool_provider_attributes.links.menu.yml +++ b/modules/lti_tool_provider_attributes/lti_tool_provider_attributes.links.menu.yml @@ -2,5 +2,5 @@ lti_tool_provider.admin.attributes: title: LTI attributes mapping parent: lti_tool_provider.admin description: Administer LTI attributes mapping. - route_name: lti_tool_provider.admin.attributes + route_name: lti_tool_provider.admin.attributes.lti_v1.0 weight: 3 diff --git a/modules/lti_tool_provider_attributes/lti_tool_provider_attributes.links.task.yml b/modules/lti_tool_provider_attributes/lti_tool_provider_attributes.links.task.yml new file mode 100644 index 0000000..675c3fe --- /dev/null +++ b/modules/lti_tool_provider_attributes/lti_tool_provider_attributes.links.task.yml @@ -0,0 +1,9 @@ +lti_tool_provider.admin.attributes.lti_v1.0_settings_form: + title: 'LTI 1.0/1.1' + route_name: lti_tool_provider.admin.attributes.lti_v1.0 + base_route: lti_tool_provider.admin.attributes.lti_v1.0 + +lti_tool_provider.admin.attributes.lti_v1.3_settings_form: + title: 'LTI 1.3' + route_name: lti_tool_provider.admin.attributes.lti_v1.3 + base_route: lti_tool_provider.admin.attributes.lti_v1.0 diff --git a/modules/lti_tool_provider_attributes/lti_tool_provider_attributes.routing.yml b/modules/lti_tool_provider_attributes/lti_tool_provider_attributes.routing.yml index 3a6837b..84ce71d 100644 --- a/modules/lti_tool_provider_attributes/lti_tool_provider_attributes.routing.yml +++ b/modules/lti_tool_provider_attributes/lti_tool_provider_attributes.routing.yml @@ -1,7 +1,15 @@ -lti_tool_provider.admin.attributes: - path: /admin/config/lti-tool-provider/attributes +lti_tool_provider.admin.attributes.lti_v1.0: + path: /admin/config/lti-tool-provider/attributes/v1p0 defaults: - _form: \Drupal\lti_tool_provider_attributes\Form\LtiToolProviderAttributesSettingsForm - _title: Mapped Attributes + _form: \Drupal\lti_tool_provider_attributes\Form\V1p0LtiToolProviderAttributesSettingsForm + _title: Mapped Attributes for LTI 1.0/1.1 + requirements: + _permission: administer lti_tool_provider module + +lti_tool_provider.admin.attributes.lti_v1.3: + path: /admin/config/lti-tool-provider/attributes/v1p3 + defaults: + _form: \Drupal\lti_tool_provider_attributes\Form\V1p3LtiToolProviderAttributesSettingsForm + _title: Mapped Attributes for LTI 1.3 requirements: _permission: administer lti_tool_provider module diff --git a/modules/lti_tool_provider_attributes/src/Event/LtiToolProviderAttributesEvent.php b/modules/lti_tool_provider_attributes/src/Event/LtiToolProviderAttributesEvent.php index 5e2f5f6..c495a48 100644 --- a/modules/lti_tool_provider_attributes/src/Event/LtiToolProviderAttributesEvent.php +++ b/modules/lti_tool_provider_attributes/src/Event/LtiToolProviderAttributesEvent.php @@ -5,60 +5,59 @@ namespace Drupal\lti_tool_provider_attributes\Event; use Drupal\lti_tool_provider\LtiToolProviderEvent; use Drupal\user\UserInterface; -class LtiToolProviderAttributesEvent extends LtiToolProviderEvent -{ - const EVENT_NAME = 'LTI_TOOL_PROVIDER_ATTRIBUTES_EVENT'; +/** + * Implementation LtiToolProviderAttributesEvent class. + */ +class LtiToolProviderAttributesEvent extends LtiToolProviderEvent { + const EVENT_NAME = 'LTI_TOOL_PROVIDER_ATTRIBUTES_EVENT'; + + /** + * @var array + */ + private $context; + + /** + * @var \Drupal\user\UserInterface + */ + private $user; + + /** + * LtiToolProviderAttributesEvent constructor. + * + * @param array $context + * @param \Drupal\user\UserInterface $user + */ + public function __construct(array $context, UserInterface $user) { + $this->setContext($context); + $this->setUser($user); + } + + /** + * @return array + */ + public function getContext(): array { + return $this->context; + } + + /** + * @param array $context + */ + public function setContext(array $context) { + $this->context = $context; + } + + /** + * @return \Drupal\user\UserInterface + */ + public function getUser(): UserInterface { + return $this->user; + } + + /** + * @param \Drupal\user\UserInterface $user + */ + public function setUser(UserInterface $user): void { + $this->user = $user; + } - /** - * @var array - */ - private $context; - - /** - * @var UserInterface - */ - private $user; - - /** - * LtiToolProviderAttributesEvent constructor. - * @param array $context - * @param UserInterface $user - */ - public function __construct(array $context, UserInterface $user) - { - $this->setContext($context); - $this->setUser($user); - } - - /** - * @return array - */ - public function getContext(): array - { - return $this->context; - } - - /** - * @param array $context - */ - public function setContext(array $context) - { - $this->context = $context; - } - - /** - * @return UserInterface - */ - public function getUser(): UserInterface - { - return $this->user; - } - - /** - * @param UserInterface $user - */ - public function setUser(UserInterface $user): void - { - $this->user = $user; - } } diff --git a/modules/lti_tool_provider_attributes/src/EventSubscriber/LtiToolProviderAttributesEventSubscriber.php b/modules/lti_tool_provider_attributes/src/EventSubscriber/LtiToolProviderAttributesEventSubscriber.php index 1f2eeea..0612023 100644 --- a/modules/lti_tool_provider_attributes/src/EventSubscriber/LtiToolProviderAttributesEventSubscriber.php +++ b/modules/lti_tool_provider_attributes/src/EventSubscriber/LtiToolProviderAttributesEventSubscriber.php @@ -11,76 +11,100 @@ use Exception; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -class LtiToolProviderAttributesEventSubscriber implements EventSubscriberInterface -{ - /** - * @var ConfigFactoryInterface - */ - protected $configFactory; - - /** - * @var EventDispatcherInterface - */ - protected $eventDispatcher; - - /** - * LtiToolProviderAttributesEventSubscriber constructor. - * @param ConfigFactoryInterface $configFactory - * @param EventDispatcherInterface $eventDispatcher - */ - public function __construct( - ConfigFactoryInterface $configFactory, - EventDispatcherInterface $eventDispatcher - ) { - $this->configFactory = $configFactory; - $this->eventDispatcher = $eventDispatcher; +/** + * Implementation LtiToolProviderAttributesEventSubscriber class. + * + * @package Drupal\lti_tool_provider_attributes\EventSubscriber + */ +class LtiToolProviderAttributesEventSubscriber implements EventSubscriberInterface { + + /** + * @var ConfigFactoryInterface + */ + protected $configFactory; + + /** + * @var EventDispatcherInterface + */ + protected $eventDispatcher; + + /** + * LtiToolProviderAttributesEventSubscriber constructor. + * + * @param ConfigFactoryInterface $configFactory + * @param EventDispatcherInterface $eventDispatcher + */ + public function __construct( + ConfigFactoryInterface $configFactory, + EventDispatcherInterface $eventDispatcher + ) { + $this->configFactory = $configFactory; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + LtiToolProviderAuthenticatedEvent::EVENT_NAME => 'onAuthenticated', + ]; + } + + /** + * @param LtiToolProviderAuthenticatedEvent $event + */ + public function onAuthenticated(LtiToolProviderAuthenticatedEvent $event) { + $context = $event->getContext(); + $lti_version = get_lti_version_by_launch_data($context); + + if (($lti_version === 'v1p0')) { + $mapped_attributes = $this->configFactory->get('lti_tool_provider_attributes.settings')->get('v1p0_mapped_attributes'); } - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - return [ - LtiToolProviderAuthenticatedEvent::EVENT_NAME => 'onAuthenticated', - ]; + if ($lti_version === 'v1p3') { + $mapped_attributes = $this->configFactory->get('lti_tool_provider_attributes.settings')->get('v1p3_mapped_attributes'); } - /** - * @param LtiToolProviderAuthenticatedEvent $event - */ - public function onAuthenticated(LtiToolProviderAuthenticatedEvent $event) - { - $mapped_attributes = $this->configFactory->get('lti_tool_provider_attributes.settings')->get('mapped_attributes'); - $context = $event->getContext(); - $user = $event->getUser(); - - if ($user->getDisplayName() === 'ltiuser') { - return; - } + $user = $event->getUser(); + + if ($user->getDisplayName() === 'ltiuser') { + return; + } - if (!$mapped_attributes || !count($mapped_attributes)) { - return; + if (!$mapped_attributes || !count($mapped_attributes)) { + return; + } + + foreach ($mapped_attributes as $user_attribute => $lti_attribute) { + if (($lti_version === 'v1p0')) { + if (isset($context[$mapped_attributes[$user_attribute]]) && !empty($context[$mapped_attributes[$user_attribute]])) { + $user->set($user_attribute, $context[$mapped_attributes[$user_attribute]]); } + } - foreach ($mapped_attributes as $user_attribute => $lti_attribute) { - if (isset($context[$mapped_attributes[$user_attribute]]) && !empty($context[$mapped_attributes[$user_attribute]])) { - $user->set($user_attribute, $context[$mapped_attributes[$user_attribute]]); - } + if ($lti_version === 'v1p3') { + $context_data = get_data_from_context_by_lti_attribute($context, $lti_attribute); + + if (isset($context_data) && !empty($context_data)) { + $user->set($user_attribute, $context_data); } + } + } - try { - $attributesEvent = new LtiToolProviderAttributesEvent($context, $user); - LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $attributesEvent); + try { + $attributesEvent = new LtiToolProviderAttributesEvent($context, $user); + LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $attributesEvent); - if ($attributesEvent->isCancelled()) { - throw new Exception($event->getMessage()); - } + if ($attributesEvent->isCancelled()) { + throw new Exception($event->getMessage()); + } - $user->save(); - } - catch (Exception $e) { - Drupal::logger('lti_tool_provider_attributes')->error($e->getMessage()); - } + $user->save(); } + catch (Exception $e) { + Drupal::logger('lti_tool_provider_attributes')->error($e->getMessage()); + } + } + } diff --git a/modules/lti_tool_provider_attributes/src/Form/LtiToolProviderAttributesSettingsForm.php b/modules/lti_tool_provider_attributes/src/Form/LtiToolProviderAttributesSettingsForm.php deleted file mode 100644 index 89bcbda..0000000 --- a/modules/lti_tool_provider_attributes/src/Form/LtiToolProviderAttributesSettingsForm.php +++ /dev/null @@ -1,89 +0,0 @@ -config('lti_tool_provider_attributes.settings'); - $mapped_attributes = $settings->get('mapped_attributes'); - - $lti_launch = $this->config('lti_tool_provider.settings')->get('lti_launch'); - - $form['mapped_attributes'] = [ - '#type' => 'table', - '#tree' => true, - '#caption' => t( - 'This page allows you to map LTI attrubutes to Drupal user attributes. This is applied every time a user logs in via LTI.' - ), - '#header' => [t('User Field'), t('LTI Attribute')], - ]; - - /* @var $entityManager Drupal\Core\Entity\EntityFieldManagerInterface */ - $entityManager = Drupal::service('entity_field.manager'); - $userFieldDefinitions = $entityManager->getFieldDefinitions('user', 'user'); - foreach ($userFieldDefinitions as $key => $field) { - $type = $field->getType(); - if ($type === 'string') { - $form['mapped_attributes'][$key] = [ - 'user_attribute' => [ - '#type' => 'item', - '#title' => $field->getLabel(), - ], - 'lti_attribute' => [ - '#type' => 'select', - '#required' => false, - '#empty_option' => t('None'), - '#empty_value' => true, - '#default_value' => $mapped_attributes[$key], - '#options' => array_combine($lti_launch, $lti_launch), - ], - ]; - } - } - - return parent::buildForm($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) - { - $settings = $this->config('lti_tool_provider_attributes.settings'); - $lti_launch = $this->config('lti_tool_provider.settings')->get('lti_launch'); - - $mapped_attributes = []; - foreach ($form_state->getValue('mapped_attributes') as $key => $value) { - if (in_array($value['lti_attribute'], $lti_launch)) { - $mapped_attributes[$key] = $value['lti_attribute']; - } - } - - $settings->set('mapped_attributes', $mapped_attributes)->save(); - } -} diff --git a/modules/lti_tool_provider_attributes/src/Form/V1p0LtiToolProviderAttributesSettingsForm.php b/modules/lti_tool_provider_attributes/src/Form/V1p0LtiToolProviderAttributesSettingsForm.php new file mode 100644 index 0000000..649140a --- /dev/null +++ b/modules/lti_tool_provider_attributes/src/Form/V1p0LtiToolProviderAttributesSettingsForm.php @@ -0,0 +1,91 @@ +config('lti_tool_provider_attributes.settings'); + $mapped_attributes = $settings->get('v1p0_mapped_attributes'); + + $lti_launch = $this->config('lti_tool_provider.settings')->get('lti_launch_v1p0'); + + $form['mapped_attributes'] = [ + '#type' => 'table', + '#tree' => TRUE, + '#caption' => t( + 'This page allows you to map LTI attrubutes to Drupal user attributes. This is applied every time a user logs in via LTI.' + ), + '#header' => [t('User Field'), t('LTI Attribute')], + ]; + + /* @var $entityManager Drupal\Core\Entity\EntityFieldManagerInterface */ + $entityManager = Drupal::service('entity_field.manager'); + $userFieldDefinitions = $entityManager->getFieldDefinitions('user', 'user'); + foreach ($userFieldDefinitions as $key => $field) { + $type = $field->getType(); + if ($type === 'string') { + $form['mapped_attributes'][$key] = [ + 'user_attribute' => [ + '#type' => 'item', + '#title' => $field->getLabel(), + ], + 'lti_attribute' => [ + '#type' => 'select', + '#required' => FALSE, + '#empty_option' => t('None'), + '#empty_value' => TRUE, + '#default_value' => isset($mapped_attributes[$key]) ? $mapped_attributes[$key] : '', + '#options' => array_combine($lti_launch, $lti_launch), + ], + ]; + } + } + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $settings = $this->config('lti_tool_provider_attributes.settings'); + $lti_launch = $this->config('lti_tool_provider.settings')->get('lti_launch_v1p0'); + + $mapped_attributes = []; + foreach ($form_state->getValue('mapped_attributes') as $key => $value) { + if (in_array($value['lti_attribute'], $lti_launch)) { + $mapped_attributes[$key] = $value['lti_attribute']; + } + } + + $settings->set('v1p0_mapped_attributes', $mapped_attributes)->save(); + } + +} diff --git a/modules/lti_tool_provider_attributes/src/Form/V1p3LtiToolProviderAttributesSettingsForm.php b/modules/lti_tool_provider_attributes/src/Form/V1p3LtiToolProviderAttributesSettingsForm.php new file mode 100644 index 0000000..98d9e8d --- /dev/null +++ b/modules/lti_tool_provider_attributes/src/Form/V1p3LtiToolProviderAttributesSettingsForm.php @@ -0,0 +1,91 @@ +config('lti_tool_provider_attributes.settings'); + $mapped_attributes = $settings->get('v1p3_mapped_attributes'); + + $lti_launch = $this->config('lti_tool_provider.settings')->get('lti_launch_v1p3'); + + $form['mapped_attributes'] = [ + '#type' => 'table', + '#tree' => TRUE, + '#caption' => t( + 'This page allows you to map LTI attrubutes to Drupal user attributes. This is applied every time a user logs in via LTI.' + ), + '#header' => [t('User Field'), t('LTI Attribute')], + ]; + + /** @var Drupal\Core\Entity\EntityFieldManagerInterface $entityManager */ + $entityManager = Drupal::service('entity_field.manager'); + $userFieldDefinitions = $entityManager->getFieldDefinitions('user', 'user'); + foreach ($userFieldDefinitions as $key => $field) { + $type = $field->getType(); + if ($type === 'string') { + $form['mapped_attributes'][$key] = [ + 'user_attribute' => [ + '#type' => 'item', + '#title' => $field->getLabel(), + ], + 'lti_attribute' => [ + '#type' => 'select', + '#required' => FALSE, + '#empty_option' => t('None'), + '#empty_value' => TRUE, + '#default_value' => isset($mapped_attributes[$key]) ? $mapped_attributes[$key] : '', + '#options' => array_combine($lti_launch, $lti_launch), + ], + ]; + } + } + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $settings = $this->config('lti_tool_provider_attributes.settings'); + $lti_launch = $this->config('lti_tool_provider.settings')->get('lti_launch_v1p3'); + + $mapped_attributes = []; + foreach ($form_state->getValue('mapped_attributes') as $key => $value) { + if (in_array($value['lti_attribute'], $lti_launch)) { + $mapped_attributes[$key] = $value['lti_attribute']; + } + } + + $settings->set('v1p3_mapped_attributes', $mapped_attributes)->save(); + } + +} diff --git a/modules/lti_tool_provider_provision/lti_tool_provider_provision.links.menu.yml b/modules/lti_tool_provider_provision/lti_tool_provider_provision.links.menu.yml index 4c7a3aa..ded54c3 100644 --- a/modules/lti_tool_provider_provision/lti_tool_provider_provision.links.menu.yml +++ b/modules/lti_tool_provider_provision/lti_tool_provider_provision.links.menu.yml @@ -2,5 +2,5 @@ lti_tool_provider.admin.provision: title: LTI entity provisioning parent: lti_tool_provider.admin description: Administer LTI entity provisioning. - route_name: lti_tool_provider.admin.provision + route_name: lti_tool_provider.admin.provision.lti_v1.0 weight: 4 diff --git a/modules/lti_tool_provider_provision/lti_tool_provider_provision.links.task.yml b/modules/lti_tool_provider_provision/lti_tool_provider_provision.links.task.yml new file mode 100644 index 0000000..2426db7 --- /dev/null +++ b/modules/lti_tool_provider_provision/lti_tool_provider_provision.links.task.yml @@ -0,0 +1,9 @@ +lti_tool_provider.admin.provision.lti_v1.0_settings_form: + title: 'LTI 1.0/1.1' + route_name: lti_tool_provider.admin.provision.lti_v1.0 + base_route: lti_tool_provider.admin.provision.lti_v1.0 + +lti_tool_provider.admin.provision.lti_v1.3_settings_form: + title: 'LTI 1.3' + route_name: lti_tool_provider.admin.provision.lti_v1.3 + base_route: lti_tool_provider.admin.provision.lti_v1.0 diff --git a/modules/lti_tool_provider_provision/lti_tool_provider_provision.module b/modules/lti_tool_provider_provision/lti_tool_provider_provision.module index 51848c3..ef1f869 100644 --- a/modules/lti_tool_provider_provision/lti_tool_provider_provision.module +++ b/modules/lti_tool_provider_provision/lti_tool_provider_provision.module @@ -7,23 +7,35 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\lti_tool_provider_provision\Entity\LtiToolProviderProvision; -use Drupal\lti_tool_provider_provision\Services\ProvisionService; /** * Implements hook_entity_predelete(). - * @param EntityInterface $entity + * + * @param \Drupal\Core\Entity\EntityInterface $entity */ -function lti_tool_provider_provision_entity_predelete(EntityInterface $entity) -{ - try { - /* @var $provisionService ProvisionService */ - $provisionService = Drupal::service('lti_tool_provider_provision.provision'); - $provision = $provisionService->getProvisionFromEntity($entity); - if ($provision && $provision instanceof LtiToolProviderProvision) { - $provision->delete(); - } - } - catch (Exception $e) { - Drupal::logger('lti_tool_provider_provision')->error($e->getMessage()); +function lti_tool_provider_provision_entity_predelete(EntityInterface $entity) { + try { + /** @var ProvisionService $provisionService */ + $provisionService = Drupal::service('lti_tool_provider_provision.provision'); + $provision = $provisionService->getProvisionFromEntity($entity); + if ($provision && $provision instanceof LtiToolProviderProvision) { + $provision->delete(); } + } + catch (Exception $e) { + Drupal::logger('lti_tool_provider_provision')->error($e->getMessage()); + } +} + +/** + * To avoid key contains a dot/colon which in array which is not supported. + * + * Replace all '.' with '__' and all ':' with '___'. + * + * @param $key + * + * @return string|string[]|null + */ +function encode_key($key) { + return preg_replace('/\./', '__', preg_replace('/\:/', '___', $key)); } diff --git a/modules/lti_tool_provider_provision/lti_tool_provider_provision.routing.yml b/modules/lti_tool_provider_provision/lti_tool_provider_provision.routing.yml index 657d431..84c6e72 100644 --- a/modules/lti_tool_provider_provision/lti_tool_provider_provision.routing.yml +++ b/modules/lti_tool_provider_provision/lti_tool_provider_provision.routing.yml @@ -1,7 +1,15 @@ -lti_tool_provider.admin.provision: - path: /admin/config/lti-tool-provider/provision +lti_tool_provider.admin.provision.lti_v1.0: + path: /admin/config/lti-tool-provider/provision/v1p0 defaults: - _form: \Drupal\lti_tool_provider_provision\Form\LtiToolProviderProvisionSettingsForm - _title: LTI Provisionable Entities + _form: \Drupal\lti_tool_provider_provision\Form\V1p0LtiToolProviderProvisionSettingsForm + _title: LTI Provisionable Entities for LTI 1.0/1.1 + requirements: + _permission: administer lti_tool_provider module + +lti_tool_provider.admin.provision.lti_v1.3: + path: /admin/config/lti-tool-provider/provision/v1p3 + defaults: + _form: \Drupal\lti_tool_provider_provision\Form\V1p3LtiToolProviderProvisionSettingsForm + _title: LTI Provisionable Entities for LTI 1.3 requirements: _permission: administer lti_tool_provider module diff --git a/modules/lti_tool_provider_provision/src/Entity/LtiToolProviderProvision.php b/modules/lti_tool_provider_provision/src/Entity/LtiToolProviderProvision.php index c8496a7..f1c2d67 100644 --- a/modules/lti_tool_provider_provision/src/Entity/LtiToolProviderProvision.php +++ b/modules/lti_tool_provider_provision/src/Entity/LtiToolProviderProvision.php @@ -20,96 +20,96 @@ use Drupal\Core\Entity\EntityTypeInterface; * }, * ) */ -class LtiToolProviderProvision extends ContentEntityBase implements ContentEntityInterface -{ - /** - * {@inheritdoc} - */ - public static function baseFieldDefinitions(EntityTypeInterface $entity_type): array - { - $fields = parent::baseFieldDefinitions($entity_type); +class LtiToolProviderProvision extends ContentEntityBase implements ContentEntityInterface { - $fields['consumer_id'] = BaseFieldDefinition::create('string') - ->setLabel(t('Consumer Id')) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ); + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type): array { + $fields = parent::baseFieldDefinitions($entity_type); - $fields['context_id'] = BaseFieldDefinition::create('string') - ->setLabel(t('Context Id')) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ); + $fields['consumer_id'] = BaseFieldDefinition::create('string') + ->setLabel(t('Consumer Id')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); - $fields['context_label'] = BaseFieldDefinition::create('string') - ->setLabel(t('Context Label')) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ); + $fields['context_id'] = BaseFieldDefinition::create('string') + ->setLabel(t('Context Id')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); - $fields['context_title'] = BaseFieldDefinition::create('string') - ->setLabel(t('Context Title')) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ); + $fields['context_label'] = BaseFieldDefinition::create('string') + ->setLabel(t('Context Label')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); - $fields['resource_link_id'] = BaseFieldDefinition::create('string') - ->setLabel(t('Resource Link Id')) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ); + $fields['context_title'] = BaseFieldDefinition::create('string') + ->setLabel(t('Context Title')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); - $fields['resource_link_title'] = BaseFieldDefinition::create('string') - ->setLabel(t('Resource Link Label')) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ); + $fields['resource_link_id'] = BaseFieldDefinition::create('string') + ->setLabel(t('Resource Link Id')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); - $fields['provision_type'] = BaseFieldDefinition::create('string') - ->setLabel(t('Provision Type')) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ); + $fields['resource_link_title'] = BaseFieldDefinition::create('string') + ->setLabel(t('Resource Link Label')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); - $fields['provision_bundle'] = BaseFieldDefinition::create('string') - ->setLabel(t('Provision Bundle')) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ); + $fields['provision_type'] = BaseFieldDefinition::create('string') + ->setLabel(t('Provision Type')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); - $fields['provision_id'] = BaseFieldDefinition::create('string') - ->setLabel(t('Provision Id')) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ); + $fields['provision_bundle'] = BaseFieldDefinition::create('string') + ->setLabel(t('Provision Bundle')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); + + $fields['provision_id'] = BaseFieldDefinition::create('string') + ->setLabel(t('Provision Id')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); + + return $fields; + } - return $fields; - } } diff --git a/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionCreateProvisionEvent.php b/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionCreateProvisionEvent.php index 44baf85..7bb1406 100644 --- a/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionCreateProvisionEvent.php +++ b/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionCreateProvisionEvent.php @@ -5,60 +5,59 @@ namespace Drupal\lti_tool_provider_provision\Event; use Drupal\Core\Entity\EntityInterface; use Drupal\lti_tool_provider\LtiToolProviderEvent; -class LtiToolProviderProvisionCreateProvisionEvent extends LtiToolProviderEvent -{ - const EVENT_NAME = 'LTI_TOOL_PROVIDER_PROVISION_CREATE_PROVISION_EVENT'; +/** + * Implementation LtiToolProviderProvisionCreateProvisionEvent class. + */ +class LtiToolProviderProvisionCreateProvisionEvent extends LtiToolProviderEvent { + const EVENT_NAME = 'LTI_TOOL_PROVIDER_PROVISION_CREATE_PROVISION_EVENT'; + + /** + * @var array + */ + private $context; + + /** + * @var \Drupal\Core\Entity\EntityInterface + */ + private $entity; + + /** + * LtiToolProviderProvisionCreateProvisionEvent constructor. + * + * @param array $context + * @param \Drupal\Core\Entity\EntityInterface $entity + */ + public function __construct(array $context, EntityInterface $entity) { + $this->setContext($context); + $this->setEntity($entity); + } + + /** + * @return array + */ + public function getContext(): array { + return $this->context; + } + + /** + * @param array $context + */ + public function setContext(array $context) { + $this->context = $context; + } + + /** + * @return \Drupal\Core\Entity\EntityInterface + */ + public function getEntity(): EntityInterface { + return $this->entity; + } + + /** + * @param \Drupal\Core\Entity\EntityInterface $entity + */ + public function setEntity(EntityInterface $entity): void { + $this->entity = $entity; + } - /** - * @var array - */ - private $context; - - /** - * @var EntityInterface - */ - private $entity; - - /** - * LtiToolProviderProvisionCreateProvisionEvent constructor. - * @param array $context - * @param EntityInterface $entity - */ - public function __construct(array $context, EntityInterface $entity) - { - $this->setContext($context); - $this->setEntity($entity); - } - - /** - * @return array - */ - public function getContext(): array - { - return $this->context; - } - - /** - * @param array $context - */ - public function setContext(array $context) - { - $this->context = $context; - } - - /** - * @return EntityInterface - */ - public function getEntity(): EntityInterface - { - return $this->entity; - } - - /** - * @param EntityInterface $entity - */ - public function setEntity(EntityInterface $entity): void - { - $this->entity = $entity; - } } diff --git a/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionCreateProvisionedEntityEvent.php b/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionCreateProvisionedEntityEvent.php index c4d997f..518c569 100644 --- a/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionCreateProvisionedEntityEvent.php +++ b/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionCreateProvisionedEntityEvent.php @@ -5,60 +5,59 @@ namespace Drupal\lti_tool_provider_provision\Event; use Drupal\Core\Entity\EntityInterface; use Drupal\lti_tool_provider\LtiToolProviderEvent; -class LtiToolProviderProvisionCreateProvisionedEntityEvent extends LtiToolProviderEvent -{ - const EVENT_NAME = 'LTI_TOOL_PROVIDER_PROVISION_CREATE_PROVISIONED_ENTITY_EVENT'; +/** + * Implementation LtiToolProviderProvisionCreateProvisionedEntityEvent class. + */ +class LtiToolProviderProvisionCreateProvisionedEntityEvent extends LtiToolProviderEvent { + const EVENT_NAME = 'LTI_TOOL_PROVIDER_PROVISION_CREATE_PROVISIONED_ENTITY_EVENT'; + + /** + * @var array + */ + private $context; + + /** + * @var \Drupal\Core\Entity\EntityInterface + */ + private $entity; + + /** + * LtiToolProviderProvisionCreateProvisionedEntityEvent constructor. + * + * @param array $context + * @param \Drupal\Core\Entity\EntityInterface $entity + */ + public function __construct(array $context, EntityInterface $entity) { + $this->setContext($context); + $this->setEntity($entity); + } + + /** + * @return array + */ + public function getContext(): array { + return $this->context; + } + + /** + * @param array $context + */ + public function setContext(array $context) { + $this->context = $context; + } + + /** + * @return \Drupal\Core\Entity\EntityInterface + */ + public function getEntity(): EntityInterface { + return $this->entity; + } + + /** + * @param \Drupal\Core\Entity\EntityInterface $entity + */ + public function setEntity(EntityInterface $entity): void { + $this->entity = $entity; + } - /** - * @var array - */ - private $context; - - /** - * @var EntityInterface - */ - private $entity; - - /** - * LtiToolProviderProvisionCreateProvisionedEntityEvent constructor. - * @param array $context - * @param EntityInterface $entity - */ - public function __construct(array $context, EntityInterface $entity) - { - $this->setContext($context); - $this->setEntity($entity); - } - - /** - * @return array - */ - public function getContext(): array - { - return $this->context; - } - - /** - * @param array $context - */ - public function setContext(array $context) - { - $this->context = $context; - } - - /** - * @return EntityInterface - */ - public function getEntity(): EntityInterface - { - return $this->entity; - } - - /** - * @param EntityInterface $entity - */ - public function setEntity(EntityInterface $entity): void - { - $this->entity = $entity; - } } diff --git a/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionEvent.php b/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionEvent.php index 408b1bc..62c088c 100644 --- a/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionEvent.php +++ b/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionEvent.php @@ -5,83 +5,80 @@ namespace Drupal\lti_tool_provider_provision\Event; use Drupal\Core\Entity\EntityInterface; use Drupal\lti_tool_provider\LtiToolProviderEvent; -class LtiToolProviderProvisionEvent extends LtiToolProviderEvent -{ - const EVENT_NAME = 'LTI_TOOL_PROVIDER_PROVISION_EVENT'; +/** + * Implementation LtiToolProviderProvisionEvent class. + */ +class LtiToolProviderProvisionEvent extends LtiToolProviderEvent { + const EVENT_NAME = 'LTI_TOOL_PROVIDER_PROVISION_EVENT'; - /** - * @var array - */ - private $context; + /** + * @var array + */ + private $context; - /** - * @var EntityInterface - */ - private $entity; + /** + * @var \Drupal\Core\Entity\EntityInterface + */ + private $entity; - /** - * @var string - */ - private $destination; + /** + * @var string + */ + private $destination; - /** - * LtiToolProviderProvisionEvent constructor. - * @param array $context - * @param EntityInterface $entity - * @param string $destination - */ - public function __construct(array $context, EntityInterface $entity, string $destination) - { - $this->setContext($context); - $this->setEntity($entity); - $this->setDestination($destination); - } + /** + * LtiToolProviderProvisionEvent constructor. + * + * @param array $context + * @param \Drupal\Core\Entity\EntityInterface $entity + * @param string $destination + */ + public function __construct(array $context, EntityInterface $entity, string $destination) { + $this->setContext($context); + $this->setEntity($entity); + $this->setDestination($destination); + } - /** - * @return array - */ - public function getContext(): array - { - return $this->context; - } + /** + * @return array + */ + public function getContext(): array { + return $this->context; + } - /** - * @param array $context - */ - public function setContext(array $context) - { - $this->context = $context; - } + /** + * @param array $context + */ + public function setContext(array $context) { + $this->context = $context; + } - /** - * @return EntityInterface - */ - public function getEntity(): EntityInterface - { - return $this->entity; - } + /** + * @return \Drupal\Core\Entity\EntityInterface + */ + public function getEntity(): EntityInterface { + return $this->entity; + } - /** - * @param EntityInterface $entity - */ - public function setEntity(EntityInterface $entity): void - { - $this->entity = $entity; - } + /** + * @param \Drupal\Core\Entity\EntityInterface $entity + */ + public function setEntity(EntityInterface $entity): void { + $this->entity = $entity; + } - /** - * @return string - */ - public function getDestination(): string - { - return $this->destination; - } + /** + * @return string + */ + public function getDestination(): string { + return $this->destination; + } + + /** + * @param string $destination + */ + public function setDestination(string $destination): void { + $this->destination = $destination; + } - /** - * @param string $destination - */ - public function setDestination(string $destination): void - { - $this->destination = $destination; - } } diff --git a/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionSyncProvisionedEntityEvent.php b/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionSyncProvisionedEntityEvent.php index 9fd4628..8fcefbf 100644 --- a/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionSyncProvisionedEntityEvent.php +++ b/modules/lti_tool_provider_provision/src/Event/LtiToolProviderProvisionSyncProvisionedEntityEvent.php @@ -5,60 +5,59 @@ namespace Drupal\lti_tool_provider_provision\Event; use Drupal\Core\Entity\EntityInterface; use Drupal\lti_tool_provider\LtiToolProviderEvent; -class LtiToolProviderProvisionSyncProvisionedEntityEvent extends LtiToolProviderEvent -{ - const EVENT_NAME = 'LTI_TOOL_PROVIDER_PROVISION_SYNC_PROVISIONED_ENTITY_EVENT'; +/** + * Implementation LtiToolProviderProvisionSyncProvisionedEntityEvent class. + */ +class LtiToolProviderProvisionSyncProvisionedEntityEvent extends LtiToolProviderEvent { + const EVENT_NAME = 'LTI_TOOL_PROVIDER_PROVISION_SYNC_PROVISIONED_ENTITY_EVENT'; + + /** + * @var array + */ + private $context; + + /** + * @var \Drupal\Core\Entity\EntityInterface + */ + private $entity; + + /** + * LtiToolProviderProvisionSyncProvisionedEntityEvent constructor. + * + * @param array $context + * @param \Drupal\Core\Entity\EntityInterface $entity + */ + public function __construct(array $context, EntityInterface $entity) { + $this->setContext($context); + $this->setEntity($entity); + } + + /** + * @return array + */ + public function getContext(): array { + return $this->context; + } + + /** + * @param array $context + */ + public function setContext(array $context) { + $this->context = $context; + } + + /** + * @return \Drupal\Core\Entity\EntityInterface + */ + public function getEntity(): EntityInterface { + return $this->entity; + } + + /** + * @param \Drupal\Core\Entity\EntityInterface $entity + */ + public function setEntity(EntityInterface $entity): void { + $this->entity = $entity; + } - /** - * @var array - */ - private $context; - - /** - * @var EntityInterface - */ - private $entity; - - /** - * LtiToolProviderProvisionSyncProvisionedEntityEvent constructor. - * @param array $context - * @param EntityInterface $entity - */ - public function __construct(array $context, EntityInterface $entity) - { - $this->setContext($context); - $this->setEntity($entity); - } - - /** - * @return array - */ - public function getContext(): array - { - return $this->context; - } - - /** - * @param array $context - */ - public function setContext(array $context) - { - $this->context = $context; - } - - /** - * @return EntityInterface - */ - public function getEntity(): EntityInterface - { - return $this->entity; - } - - /** - * @param EntityInterface $entity - */ - public function setEntity(EntityInterface $entity): void - { - $this->entity = $entity; - } } diff --git a/modules/lti_tool_provider_provision/src/EventSubscriber/LtiToolProviderProvisionEventSubscriber.php b/modules/lti_tool_provider_provision/src/EventSubscriber/LtiToolProviderProvisionEventSubscriber.php index 7b03487..520949c 100644 --- a/modules/lti_tool_provider_provision/src/EventSubscriber/LtiToolProviderProvisionEventSubscriber.php +++ b/modules/lti_tool_provider_provision/src/EventSubscriber/LtiToolProviderProvisionEventSubscriber.php @@ -13,104 +13,143 @@ use Exception; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -class LtiToolProviderProvisionEventSubscriber implements EventSubscriberInterface -{ - /** - * @var ConfigFactoryInterface - */ - protected $configFactory; - - /** - * @var ProvisionService - */ - private $provisionService; - - /** - * @var EventDispatcherInterface - */ - protected $eventDispatcher; - - /** - * LtiToolProviderProvisionEventSubscriber constructor. - * @param ConfigFactoryInterface $configFactory - * @param ProvisionService $provisionService - * @param EventDispatcherInterface $eventDispatcher - */ - public function __construct( - ConfigFactoryInterface $configFactory, - ProvisionService $provisionService, - EventDispatcherInterface $eventDispatcher - ) { - $this->configFactory = $configFactory; - $this->provisionService = $provisionService; - $this->eventDispatcher = $eventDispatcher; +/** + * Implementation LtiToolProviderProvisionEventSubscriber class. + * + * @package Drupal\lti_tool_provider_provision\EventSubscriber + */ +class LtiToolProviderProvisionEventSubscriber implements EventSubscriberInterface { + + /** + * @var ConfigFactoryInterface + */ + protected $configFactory; + + /** + * @var ProvisionService + */ + private $provisionService; + + /** + * @var EventDispatcherInterface + */ + protected $eventDispatcher; + + /** + * LtiToolProviderProvisionEventSubscriber constructor. + * + * @param ConfigFactoryInterface $configFactory + * @param ProvisionService $provisionService + * @param EventDispatcherInterface $eventDispatcher + */ + public function __construct( + ConfigFactoryInterface $configFactory, + ProvisionService $provisionService, + EventDispatcherInterface $eventDispatcher + ) { + $this->configFactory = $configFactory; + $this->provisionService = $provisionService; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + LtiToolProviderLaunchRedirectEvent::EVENT_NAME => 'onLaunch', + LtiToolProviderProvisionCreateProvisionEvent::EVENT_NAME => 'onCreateProvision', + ]; + } + + /** + * @param LtiToolProviderLaunchRedirectEvent $event + */ + public function onLaunch(LtiToolProviderLaunchRedirectEvent $event) { + $is_entity_sync = FALSE; + $is_entity_redirect = FALSE; + $context = $event->getContext(); + $lti_version = get_lti_version_by_launch_data($context); + + if (($lti_version === 'v1p0')) { + $is_entity_sync = $this->configFactory + ->get('lti_tool_provider_provision.settings')->get('v1p0_entity_sync'); + $is_entity_redirect = $this->configFactory + ->get('lti_tool_provider_provision.settings')->get('v1p0_entity_redirect'); } - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - return [ - LtiToolProviderLaunchRedirectEvent::EVENT_NAME => 'onLaunch', - LtiToolProviderProvisionCreateProvisionEvent::EVENT_NAME => 'onCreateProvision', - ]; + if ($lti_version === 'v1p3') { + $is_entity_sync = $this->configFactory + ->get('lti_tool_provider_provision.settings')->get('v1p3_entity_sync'); + $is_entity_redirect = $this->configFactory + ->get('lti_tool_provider_provision.settings')->get('v1p3_entity_redirect'); } - /** - * @param LtiToolProviderLaunchRedirectEvent $event - */ - public function onLaunch(LtiToolProviderLaunchRedirectEvent $event) - { - $context = $event->getContext(); - - try { - if ($entity = $this->provisionService->provision($context)) { - if ($this->configFactory->get('lti_tool_provider_provision.settings')->get('entity_sync')) { - $this->provisionService->syncProvisionedEntity($context, $entity); - $entity->save(); - } - - $provisionEvent = new LtiToolProviderProvisionEvent($context, $entity, $entity->toUrl()->toString()); - LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $provisionEvent); - - if ($provisionEvent->isCancelled()) { - throw new Exception($event->getMessage()); - } - - if ($this->configFactory->get('lti_tool_provider_provision.settings')->get('entity_redirect')) { - $event->setDestination($provisionEvent->getDestination()); - } - } + try { + if ($entity = $this->provisionService->provision($context)) { + if ($is_entity_sync) { + $this->provisionService->syncProvisionedEntity($context, $entity); + $entity->save(); } - catch (Exception $e) { - $event->sendLtiError($context, $e->getMessage()); - Drupal::logger('lti_tool_provider_provision')->error($e->getMessage()); + + $provisionEvent = new LtiToolProviderProvisionEvent($context, $entity, $entity->toUrl()->toString()); + LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $provisionEvent); + + if ($provisionEvent->isCancelled()) { + throw new Exception($event->getMessage()); } - } - /** - * @param LtiToolProviderProvisionCreateProvisionEvent $event - */ - public function onCreateProvision(LtiToolProviderProvisionCreateProvisionEvent $event) - { - $context = $event->getContext(); - $access = true; - - if ($this->configFactory->get('lti_tool_provider_provision.settings')->get('allowed_roles_enabled')) { - $access = false; - $ltiRoles = parse_roles($context['roles']); - $allowedRoles = $this->configFactory->get('lti_tool_provider_provision.settings')->get('allowed_roles'); - foreach ($ltiRoles as $ltiRole) { - if (isset($allowedRoles[$ltiRole]) && $allowedRoles[$ltiRole]) { - $access = true; - break; - } - } + if ($is_entity_redirect) { + $event->setDestination($provisionEvent->getDestination()); } + } + } + catch (Exception $e) { + $event->sendLtiError($context, $e->getMessage()); + Drupal::logger('lti_tool_provider_provision')->error($e->getMessage()); + } + } + + /** + * @param LtiToolProviderProvisionCreateProvisionEvent $event + */ + public function onCreateProvision(LtiToolProviderProvisionCreateProvisionEvent $event) { + $access = TRUE; + $ltiRoles = []; + $allowedRoles = []; + $context = $event->getContext(); + $is_allowed_roles_enabled = FALSE; + $lti_version = get_lti_version_by_launch_data($context); + + if (($lti_version === 'v1p0')) { + $is_allowed_roles_enabled = $this->configFactory + ->get('lti_tool_provider_provision.settings')->get('v1p0_allowed_roles_enabled'); + $ltiRoles = parse_roles($context['roles']); + $allowedRoles = $this->configFactory->get('lti_tool_provider_provision.settings')->get('v1p0_allowed_roles'); + } + + if ($lti_version === 'v1p3') { + $is_allowed_roles_enabled = $this->configFactory + ->get('lti_tool_provider_provision.settings')->get('v1p3_allowed_roles_enabled'); + $ltiRoles = $context['https://purl.imsglobal.org/spec/lti/claim/roles']; + $allowedRoles = $this->configFactory->get('lti_tool_provider_provision.settings')->get('v1p3_allowed_roles'); + } + + if ($is_allowed_roles_enabled) { + $access = FALSE; + foreach ($ltiRoles as $ltiRole) { + $ltiRole = $lti_version === 'v1p3' ? encode_key($ltiRole) : $ltiRole; - if (!$access) { - $event->cancel('Unable to provision entity.'); + if (isset($allowedRoles[$ltiRole]) && $allowedRoles[$ltiRole]) { + $access = TRUE; + break; } + } } + + if (!$access) { + $event->cancel('Unable to provision entity.'); + } + } + } diff --git a/modules/lti_tool_provider_provision/src/Form/LtiToolProviderProvisionSettingsForm.php b/modules/lti_tool_provider_provision/src/Form/LtiToolProviderProvisionSettingsForm.php deleted file mode 100644 index 8f16f99..0000000 --- a/modules/lti_tool_provider_provision/src/Form/LtiToolProviderProvisionSettingsForm.php +++ /dev/null @@ -1,215 +0,0 @@ -config('lti_tool_provider_provision.settings'); - $lti_roles = $this->config('lti_tool_provider.settings')->get('lti_roles'); - - $entityType = $form_state->getValue('entity_type') ? $form_state->getValue('entity_type') : $settings->get('entity_type'); - $entityBundle = $form_state->getValue('entity_bundle') ? $form_state->getValue('entity_bundle') : $settings->get('entity_bundle'); - $entityRedirect = $form_state->getValue('entity_redirect') ? $form_state->getValue('entity_redirect') : $settings->get('entity_redirect'); - $entityDefaults = $form_state->getValue('entity_defaults') ? $form_state->getValue('entity_defaults') : $settings->get('entity_defaults'); - $entitySync = $form_state->getValue('entity_sync') ? $form_state->getValue('entity_sync') : $settings->get('entity_sync'); - $allowedRolesEnabled = $form_state->getValue('allowed_roles_enabled') ? $form_state->getValue('allowed_roles_enabled') : $settings->get('allowed_roles_enabled'); - $allowedRoles = $form_state->getValue('allowed_roles') ? $form_state->getValue('allowed_roles') : $settings->get('allowed_roles'); - - $form['#attributes']['id'] = uniqid($this->getFormId()); - - $options = []; - $definitions = Drupal::entityTypeManager()->getDefinitions(); - - foreach ($definitions as $definition) { - if ($definition instanceof ContentEntityType) { - $options[$definition->id()] = $definition->getLabel(); - } - } - - $form['entity_type'] = [ - '#type' => 'select', - '#title' => $this->t('Default entity type'), - '#description' => $this->t('Select the entity type to use as the default entity provision.'), - '#default_value' => $entityType, - '#empty_value' => '', - '#empty_option' => '- Select an entity type -', - '#options' => $options, - '#ajax' => [ - 'callback' => '::getEntityBundles', - 'event' => 'change', - 'wrapper' => $form['#attributes']['id'], - 'progress' => [ - 'type' => 'throbber', - ], - ], - ]; - - if ($entityType) { - $options = []; - - $bundles = Drupal::service('entity_type.bundle.info')->getBundleInfo($entityType); - foreach ($bundles as $key => $bundleInfo) { - $options[$key] = $bundleInfo['label']; - } - - $form['entity_bundle'] = [ - '#type' => 'select', - '#title' => $this->t('Default entity bundle'), - '#description' => $this->t('Select the entity bundle to use as the default entity provision.'), - '#default_value' => $entityBundle, - '#empty_value' => '', - '#empty_option' => '- Select an entity type -', - '#options' => $options, - '#ajax' => [ - 'callback' => '::getEntityBundles', - 'event' => 'change', - 'wrapper' => $form['#attributes']['id'], - 'progress' => [ - 'type' => 'throbber', - ], - ], - ]; - } - - $form['entity_redirect'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Always redirect to entity upon launch.'), - '#default_value' => $entityRedirect, - ]; - - if ($entityBundle) { - $lti_launch = $this->config('lti_tool_provider.settings')->get('lti_launch'); - - $form['entity_defaults'] = [ - '#type' => 'fieldset', - '#title' => 'Entity defaults', - '#tree' => true, - ]; - - /* @var $entityManager Drupal\Core\Entity\EntityFieldManagerInterface */ - $entityManager = Drupal::service('entity_field.manager'); - $userFieldDefinitions = $entityManager->getFieldDefinitions($entityType, $entityBundle); - foreach ($userFieldDefinitions as $key => $field) { - $type = $field->getType(); - if ($type === 'string') { - $form['entity_defaults'][$key] = [ - 'name' => [ - '#type' => 'item', - '#title' => $field->getLabel(), - ], - 'lti_attribute' => [ - '#type' => 'select', - '#required' => false, - '#empty_option' => t('None'), - '#empty_value' => true, - '#default_value' => $entityDefaults[$key], - '#options' => array_combine($lti_launch, $lti_launch), - ], - ]; - } - } - - $form['entity_sync'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Always sync entity fields from context during launch.'), - '#default_value' => $entitySync, - ]; - } - - $form['allowed_roles_enabled'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Restrict entity provision to specific LTI roles.'), - '#default_value' => $allowedRolesEnabled, - ]; - - $form['allowed_roles'] = [ - '#type' => 'details', - '#title' => 'Allowed Roles', - '#description' => $this->t('If enabled above, allow only specific LTI roles to provision entities.'), - '#tree' => true, - '#open' => false, - ]; - - foreach ($lti_roles as $ltiRole) { - $form['allowed_roles'][$ltiRole] = [ - '#type' => 'checkbox', - '#title' => $this->t($ltiRole), - '#default_value' => $allowedRoles[$ltiRole], - ]; - } - - return parent::buildForm($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) - { - $settings = $this->config('lti_tool_provider_provision.settings'); - $lti_launch = $this->config('lti_tool_provider.settings')->get('lti_launch'); - - $entityType = $form_state->getValue('entity_type'); - $entityBundle = $form_state->getValue('entity_bundle'); - $entityRedirect = $form_state->getValue('entity_redirect'); - $entitySync = $form_state->getValue('entity_sync'); - $allowedRolesEnabled = $form_state->getValue('allowed_roles_enabled'); - - $settings->set('entity_type', $entityType)->save(); - $settings->set('entity_bundle', $entityBundle)->save(); - $settings->set('entity_redirect', $entityRedirect)->save(); - $settings->set('entity_sync', $entitySync)->save(); - $settings->set('allowed_roles_enabled', $allowedRolesEnabled)->save(); - - $entityDefaults = []; - foreach ($form_state->getValue('entity_defaults') as $key => $value) { - if (in_array($value['lti_attribute'], $lti_launch)) { - $entityDefaults[$key] = $value['lti_attribute']; - } - } - $settings->set('entity_defaults', $entityDefaults)->save(); - - $allowedRoles = []; - foreach ($form_state->getValue('allowed_roles') as $key => $value) { - $allowedRoles[$key] = $value; - } - $settings->set('allowed_roles', $allowedRoles)->save(); - - parent::submitForm($form, $form_state); - } - - /** - * @param array $form - * @return array - */ - public function getEntityBundles(array $form): array - { - return $form; - } -} diff --git a/modules/lti_tool_provider_provision/src/Form/V1p0LtiToolProviderProvisionSettingsForm.php b/modules/lti_tool_provider_provision/src/Form/V1p0LtiToolProviderProvisionSettingsForm.php new file mode 100644 index 0000000..b36eaba --- /dev/null +++ b/modules/lti_tool_provider_provision/src/Form/V1p0LtiToolProviderProvisionSettingsForm.php @@ -0,0 +1,218 @@ +config('lti_tool_provider_provision.settings'); + $lti_roles = $this->config('lti_tool_provider.settings')->get('lti_roles_v1p0'); + + $entityType = $form_state->getValue('entity_type') ? $form_state->getValue('entity_type') : $settings->get('v1p0_entity_type'); + $entityBundle = $form_state->getValue('entity_bundle') ? $form_state->getValue('entity_bundle') : $settings->get('v1p0_entity_bundle'); + $entityRedirect = $form_state->getValue('entity_redirect') ? $form_state->getValue('entity_redirect') : $settings->get('v1p0_entity_redirect'); + $entityDefaults = $form_state->getValue('entity_defaults') ? $form_state->getValue('entity_defaults') : $settings->get('v1p0_entity_defaults'); + $entitySync = $form_state->getValue('entity_sync') ? $form_state->getValue('entity_sync') : $settings->get('v1p0_entity_sync'); + $allowedRolesEnabled = $form_state->getValue('allowed_roles_enabled') ? $form_state->getValue('allowed_roles_enabled') : $settings->get('v1p0_allowed_roles_enabled'); + $allowedRoles = $form_state->getValue('allowed_roles') ? $form_state->getValue('allowed_roles') : $settings->get('v1p0_allowed_roles'); + + $form['#attributes']['id'] = uniqid($this->getFormId()); + + $options = []; + $definitions = Drupal::entityTypeManager()->getDefinitions(); + + foreach ($definitions as $definition) { + if ($definition instanceof ContentEntityType) { + $options[$definition->id()] = $definition->getLabel(); + } + } + + $form['entity_type'] = [ + '#type' => 'select', + '#title' => $this->t('Default entity type'), + '#description' => $this->t('Select the entity type to use as the default entity provision.'), + '#default_value' => $entityType, + '#empty_value' => '', + '#empty_option' => '- Select an entity type -', + '#options' => $options, + '#ajax' => [ + 'callback' => '::getEntityBundles', + 'event' => 'change', + 'wrapper' => $form['#attributes']['id'], + 'progress' => [ + 'type' => 'throbber', + ], + ], + ]; + + if ($entityType) { + $options = []; + + $bundles = Drupal::service('entity_type.bundle.info')->getBundleInfo($entityType); + foreach ($bundles as $key => $bundleInfo) { + $options[$key] = $bundleInfo['label']; + } + + $form['entity_bundle'] = [ + '#type' => 'select', + '#title' => $this->t('Default entity bundle'), + '#description' => $this->t('Select the entity bundle to use as the default entity provision.'), + '#default_value' => $entityBundle, + '#empty_value' => '', + '#empty_option' => '- Select an entity type -', + '#options' => $options, + '#ajax' => [ + 'callback' => '::getEntityBundles', + 'event' => 'change', + 'wrapper' => $form['#attributes']['id'], + 'progress' => [ + 'type' => 'throbber', + ], + ], + ]; + } + + $form['entity_redirect'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Always redirect to entity upon launch.'), + '#default_value' => $entityRedirect, + ]; + + if ($entityBundle) { + $lti_launch = $this->config('lti_tool_provider.settings')->get('lti_launch_v1p0'); + + $form['entity_defaults'] = [ + '#type' => 'fieldset', + '#title' => 'Entity defaults', + '#tree' => TRUE, + ]; + + /** @var $entityManager Drupal\Core\Entity\EntityFieldManagerInterface */ + $entityManager = Drupal::service('entity_field.manager'); + $userFieldDefinitions = $entityManager->getFieldDefinitions($entityType, $entityBundle); + foreach ($userFieldDefinitions as $key => $field) { + $type = $field->getType(); + if ($type === 'string') { + $form['entity_defaults'][$key] = [ + 'name' => [ + '#type' => 'item', + '#title' => $field->getLabel(), + ], + 'lti_attribute' => [ + '#type' => 'select', + '#required' => FALSE, + '#empty_option' => t('None'), + '#empty_value' => TRUE, + '#default_value' => $entityDefaults[$key], + '#options' => array_combine($lti_launch, $lti_launch), + ], + ]; + } + } + + $form['entity_sync'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Always sync entity fields from context during launch.'), + '#default_value' => $entitySync, + ]; + } + + $form['allowed_roles_enabled'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Restrict entity provision to specific LTI roles.'), + '#default_value' => $allowedRolesEnabled, + ]; + + $form['allowed_roles'] = [ + '#type' => 'details', + '#title' => 'Allowed Roles', + '#description' => $this->t('If enabled above, allow only specific LTI roles to provision entities.'), + '#tree' => TRUE, + '#open' => FALSE, + ]; + + foreach ($lti_roles as $ltiRole) { + $form['allowed_roles'][$ltiRole] = [ + '#type' => 'checkbox', + '#title' => $this->t('@ltiRole', ['@ltiRole' => $ltiRole]), + '#default_value' => $allowedRoles[$ltiRole], + ]; + } + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $settings = $this->config('lti_tool_provider_provision.settings'); + $lti_launch = $this->config('lti_tool_provider.settings')->get('lti_launch_v1p0'); + + $entityType = $form_state->getValue('entity_type'); + $entityBundle = $form_state->getValue('entity_bundle'); + $entityRedirect = $form_state->getValue('entity_redirect'); + $entitySync = $form_state->getValue('entity_sync'); + $allowedRolesEnabled = $form_state->getValue('allowed_roles_enabled'); + + $settings->set('v1p0_entity_type', $entityType)->save(); + $settings->set('v1p0_entity_bundle', $entityBundle)->save(); + $settings->set('v1p0_entity_redirect', $entityRedirect)->save(); + $settings->set('v1p0_entity_sync', $entitySync)->save(); + $settings->set('v1p0_allowed_roles_enabled', $allowedRolesEnabled)->save(); + + $entityDefaults = []; + foreach ($form_state->getValue('entity_defaults') as $key => $value) { + if (in_array($value['lti_attribute'], $lti_launch)) { + $entityDefaults[$key] = $value['lti_attribute']; + } + } + $settings->set('v1p0_entity_defaults', $entityDefaults)->save(); + + $allowedRoles = []; + foreach ($form_state->getValue('allowed_roles') as $key => $value) { + $allowedRoles[$key] = $value; + } + $settings->set('v1p0_allowed_roles', $allowedRoles)->save(); + + parent::submitForm($form, $form_state); + } + + /** + * Get Entity bundle. + * + * @param array $form + * @return array + */ + public function getEntityBundles(array $form): array { + return $form; + } + +} diff --git a/modules/lti_tool_provider_provision/src/Form/V1p3LtiToolProviderProvisionSettingsForm.php b/modules/lti_tool_provider_provision/src/Form/V1p3LtiToolProviderProvisionSettingsForm.php new file mode 100644 index 0000000..47f5489 --- /dev/null +++ b/modules/lti_tool_provider_provision/src/Form/V1p3LtiToolProviderProvisionSettingsForm.php @@ -0,0 +1,215 @@ +config('lti_tool_provider_provision.settings'); + $lti_roles = $this->config('lti_tool_provider.settings')->get('lti_roles_v1p3'); + + $entityType = $form_state->getValue('entity_type') ? $form_state->getValue('entity_type') : $settings->get('v1p3_entity_type'); + $entityBundle = $form_state->getValue('entity_bundle') ? $form_state->getValue('entity_bundle') : $settings->get('v1p3_entity_bundle'); + $entityRedirect = $form_state->getValue('entity_redirect') ? $form_state->getValue('entity_redirect') : $settings->get('v1p3_entity_redirect'); + $entityDefaults = $form_state->getValue('entity_defaults') ? $form_state->getValue('entity_defaults') : $settings->get('v1p3_entity_defaults'); + $entitySync = $form_state->getValue('entity_sync') ? $form_state->getValue('entity_sync') : $settings->get('v1p3_entity_sync'); + $allowedRolesEnabled = $form_state->getValue('allowed_roles_enabled') ? $form_state->getValue('allowed_roles_enabled') : $settings->get('v1p3_allowed_roles_enabled'); + $allowedRoles = $form_state->getValue('allowed_roles') ? $form_state->getValue('allowed_roles') : $settings->get('v1p3_allowed_roles'); + + $form['#attributes']['id'] = uniqid($this->getFormId()); + + $options = []; + $definitions = Drupal::entityTypeManager()->getDefinitions(); + + foreach ($definitions as $definition) { + if ($definition instanceof ContentEntityType) { + $options[$definition->id()] = $definition->getLabel(); + } + } + + $form['entity_type'] = [ + '#type' => 'select', + '#title' => $this->t('Default entity type'), + '#description' => $this->t('Select the entity type to use as the default entity provision.'), + '#default_value' => $entityType, + '#empty_value' => '', + '#empty_option' => '- Select an entity type -', + '#options' => $options, + '#ajax' => [ + 'callback' => '::getEntityBundles', + 'event' => 'change', + 'wrapper' => $form['#attributes']['id'], + 'progress' => [ + 'type' => 'throbber', + ], + ], + ]; + + if ($entityType) { + $options = []; + + $bundles = Drupal::service('entity_type.bundle.info')->getBundleInfo($entityType); + foreach ($bundles as $key => $bundleInfo) { + $options[$key] = $bundleInfo['label']; + } + + $form['entity_bundle'] = [ + '#type' => 'select', + '#title' => $this->t('Default entity bundle'), + '#description' => $this->t('Select the entity bundle to use as the default entity provision.'), + '#default_value' => $entityBundle, + '#empty_value' => '', + '#empty_option' => '- Select an entity type -', + '#options' => $options, + '#ajax' => [ + 'callback' => '::getEntityBundles', + 'event' => 'change', + 'wrapper' => $form['#attributes']['id'], + 'progress' => [ + 'type' => 'throbber', + ], + ], + ]; + } + + $form['entity_redirect'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Always redirect to entity upon launch.'), + '#default_value' => $entityRedirect, + ]; + + if ($entityBundle) { + $lti_launch = $this->config('lti_tool_provider.settings')->get('lti_launch_v1p3'); + + $form['entity_defaults'] = [ + '#type' => 'fieldset', + '#title' => 'Entity defaults', + '#tree' => TRUE, + ]; + + /** @var Drupal\Core\Entity\EntityFieldManagerInterface $entityManager */ + $entityManager = Drupal::service('entity_field.manager'); + $userFieldDefinitions = $entityManager->getFieldDefinitions($entityType, $entityBundle); + foreach ($userFieldDefinitions as $key => $field) { + $type = $field->getType(); + if ($type === 'string') { + $form['entity_defaults'][$key] = [ + 'name' => [ + '#type' => 'item', + '#title' => $field->getLabel(), + ], + 'lti_attribute' => [ + '#type' => 'select', + '#required' => FALSE, + '#empty_option' => t('None'), + '#empty_value' => TRUE, + '#default_value' => $entityDefaults[$key], + '#options' => array_combine($lti_launch, $lti_launch), + ], + ]; + } + } + + $form['entity_sync'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Always sync entity fields from context during launch.'), + '#default_value' => $entitySync, + ]; + } + + $form['allowed_roles_enabled'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Restrict entity provision to specific LTI roles.'), + '#default_value' => $allowedRolesEnabled, + ]; + + $form['allowed_roles'] = [ + '#type' => 'details', + '#title' => 'Allowed Roles', + '#description' => $this->t('If enabled above, allow only specific LTI roles to provision entities.'), + '#tree' => TRUE, + '#open' => FALSE, + ]; + + foreach ($lti_roles as $ltiRole) { + $form['allowed_roles'][$ltiRole] = [ + '#type' => 'checkbox', + '#title' => $this->t('@ltiRole', ['@ltiRole' => $ltiRole]), + '#default_value' => $allowedRoles[encode_key($ltiRole)], + ]; + } + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $settings = $this->config('lti_tool_provider_provision.settings'); + $lti_launch = $this->config('lti_tool_provider.settings')->get('lti_launch_v1p3'); + + $entityType = $form_state->getValue('entity_type'); + $entityBundle = $form_state->getValue('entity_bundle'); + $entityRedirect = $form_state->getValue('entity_redirect'); + $entitySync = $form_state->getValue('entity_sync'); + $allowedRolesEnabled = $form_state->getValue('allowed_roles_enabled'); + + $settings->set('v1p3_entity_type', $entityType)->save(); + $settings->set('v1p3_entity_bundle', $entityBundle)->save(); + $settings->set('v1p3_entity_redirect', $entityRedirect)->save(); + $settings->set('v1p3_entity_sync', $entitySync)->save(); + $settings->set('v1p3_allowed_roles_enabled', $allowedRolesEnabled)->save(); + + $entityDefaults = []; + foreach ($form_state->getValue('entity_defaults') as $key => $value) { + if (in_array($value['lti_attribute'], $lti_launch)) { + $entityDefaults[$key] = $value['lti_attribute']; + } + } + $settings->set('v1p3_entity_defaults', $entityDefaults)->save(); + + $allowedRoles = []; + foreach ($form_state->getValue('allowed_roles') as $key => $value) { + $allowedRoles[encode_key($key)] = $value; + } + $settings->set('v1p3_allowed_roles', $allowedRoles)->save(); + + parent::submitForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function getEntityBundles(array $form): array { + return $form; + } + +} diff --git a/modules/lti_tool_provider_provision/src/LtiToolProviderProvisionAccessController.php b/modules/lti_tool_provider_provision/src/LtiToolProviderProvisionAccessController.php index 83eeafb..17ac66e 100644 --- a/modules/lti_tool_provider_provision/src/LtiToolProviderProvisionAccessController.php +++ b/modules/lti_tool_provider_provision/src/LtiToolProviderProvisionAccessController.php @@ -7,21 +7,25 @@ use Drupal\Core\Entity\EntityAccessControlHandler; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Session\AccountInterface; -class LtiToolProviderProvisionAccessController extends EntityAccessControlHandler -{ - /** - * {@inheritdoc} - */ - protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) - { - return AccessResult::allowedIfHasPermission($account, 'administer lti_tool_provider module'); - } +/** + * Implementation LtiToolProviderProvisionAccessController class. + * + * @package Drupal\lti_tool_provider + */ +class LtiToolProviderProvisionAccessController extends EntityAccessControlHandler { + + /** + * {@inheritdoc} + */ + protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { + return AccessResult::allowedIfHasPermission($account, 'administer lti_tool_provider module'); + } + + /** + * {@inheritdoc} + */ + protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { + return AccessResult::allowedIfHasPermission($account, 'administer lti_tool_provider module'); + } - /** - * {@inheritdoc} - */ - protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = null) - { - return AccessResult::allowedIfHasPermission($account, 'administer lti_tool_provider module'); - } } diff --git a/modules/lti_tool_provider_provision/src/Services/ProvisionService.php b/modules/lti_tool_provider_provision/src/Services/ProvisionService.php index 7ab6b3e..e6939df 100644 --- a/modules/lti_tool_provider_provision/src/Services/ProvisionService.php +++ b/modules/lti_tool_provider_provision/src/Services/ProvisionService.php @@ -17,206 +17,267 @@ use Drupal\lti_tool_provider_provision\Event\LtiToolProviderProvisionSyncProvisi use Exception; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -class ProvisionService -{ - /** - * @var ConfigFactoryInterface - */ - protected $configFactory; - - /** - * @var EntityTypeManagerInterface - */ - protected $entityTypeManager; - - /** - * @var EventDispatcherInterface - */ - protected $eventDispatcher; - - /** - * @var ImmutableConfig - */ - private $config; - - /** - * ProvisionService constructor. - * @param ConfigFactoryInterface $configFactory - * @param EntityTypeManagerInterface $entityTypeManager - * @param EventDispatcherInterface $eventDispatcher - */ - public function __construct( - ConfigFactoryInterface $configFactory, - EntityTypeManagerInterface $entityTypeManager, - EventDispatcherInterface $eventDispatcher - ) { - $this->configFactory = $configFactory; - $this->entityTypeManager = $entityTypeManager; - $this->eventDispatcher = $eventDispatcher; - $this->config = $configFactory->get('lti_tool_provider_provision.settings'); +/** + * Implementation ProvisionService class. + * + * @package Drupal\lti_tool_provider_provision\Services + */ +class ProvisionService { + + /** + * @var ConfigFactoryInterface + */ + protected $configFactory; + + /** + * @var EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * @var EventDispatcherInterface + */ + protected $eventDispatcher; + + /** + * @var ImmutableConfig + */ + private $config; + + /** + * ProvisionService constructor. + * + * @param ConfigFactoryInterface $configFactory + * @param EntityTypeManagerInterface $entityTypeManager + * @param EventDispatcherInterface $eventDispatcher + */ + public function __construct( + ConfigFactoryInterface $configFactory, + EntityTypeManagerInterface $entityTypeManager, + EventDispatcherInterface $eventDispatcher + ) { + $this->configFactory = $configFactory; + $this->entityTypeManager = $entityTypeManager; + $this->eventDispatcher = $eventDispatcher; + $this->config = $configFactory->get('lti_tool_provider_provision.settings'); + } + + /** + * @param array $context + * @return EntityInterface + * @throws Exception + */ + public function provision(array $context): EntityInterface { + $consumer_id = ''; + $context_id = ''; + $context_label = ''; + $context_title = ''; + $resource_link_id = ''; + $resource_link_title = ''; + $entityType = ''; + $entityBundle = ''; + $lti_version = get_lti_version_by_launch_data($context); + + if (($lti_version === 'v1p0')) { + $consumer_id = $context['consumer_id']; + $context_id = $context['context_id']; + $context_label = $context['context_label']; + $context_title = $context['context_title']; + $resource_link_id = $context['resource_link_id']; + $resource_link_title = $context['resource_link_title']; + $entityType = $this->config->get('v1p0_entity_type'); + $entityBundle = $this->config->get('v1p0_entity_bundle'); } - /** - * @param array $context - * @return EntityInterface - * @throws Exception - */ - public function provision(array $context): EntityInterface - { - $entityType = $this->config->get('entity_type'); - $entityBundle = $this->config->get('entity_bundle'); - - if ($entityType && $entityBundle && isset($context['consumer_id']) && !empty($context['consumer_id']) && isset($context['context_id']) && !empty($context['context_id']) && isset($context['resource_link_id']) && !empty($context['resource_link_id'])) { - $provision = $this->getProvisionFromContext($context); - - if (!$provision) { - $provision = LtiToolProviderProvision::create(); - - if ($provision instanceof LtiToolProviderProvision) { - $provision->set('consumer_id', $context['consumer_id']); - $provision->set('context_id', $context['context_id']); - $provision->set('context_label', $context['context_label']); - $provision->set('context_title', $context['context_title']); - $provision->set('resource_link_id', $context['resource_link_id']); - $provision->set('resource_link_title', $context['resource_link_title']); - $provision->set('provision_type', $entityType); - $provision->set('provision_bundle', $entityBundle); - } - - $event = new LtiToolProviderProvisionCreateProvisionEvent($context, $provision); - LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); - - if ($event->isCancelled()) { - throw new Exception($event->getMessage()); - } - - $provision = $event->getEntity(); - $provision->save(); - } - - if ($provision instanceof LtiToolProviderProvision) { - $entity = $provision->get('provision_id')->value ? $this->entityTypeManager->getStorage($provision->get('provision_type')->value)->load($provision->get('provision_id')->value) : null; - - if (!$entity) { - $entity = $this->createProvisionedEntity($context, $provision); - } - - $entity = $this->syncProvisionedEntity($context, $entity); - - $entity->save(); - $provision->set('provision_id', $entity->id()); - $provision->save(); - - return $entity; - } - } - - throw new Exception('Unable to provision entity.'); + if ($lti_version === 'v1p3') { + $consumer_id = $context['aud']; + $context_id = $context[LTI_CLAIM_BASE_URL . 'context']['id']; + $context_label = $context[LTI_CLAIM_BASE_URL . 'context']['label']; + $context_title = $context[LTI_CLAIM_BASE_URL . 'context']['title']; + $resource_link_id = $context[LTI_CLAIM_BASE_URL . 'resource_link']['id']; + $resource_link_title = $context[LTI_CLAIM_BASE_URL . 'resource_link']['title']; + $entityType = $this->config->get('v1p3_entity_type'); + $entityBundle = $this->config->get('v1p3_entity_bundle'); } - /** - * @param array $context - * @param EntityInterface|LtiToolProviderProvision $provision - * @return EntityInterface - * @throws InvalidPluginDefinitionException - * @throws PluginNotFoundException - * @throws Exception - */ - public function createProvisionedEntity(array $context, EntityInterface $provision): EntityInterface - { - $entityType = $provision->get('provision_type')->value; - $entityBundle = $provision->get('provision_bundle')->value; - - $bundleType = $this->entityTypeManager->getDefinition($entityType)->getKey('bundle'); - $entity = $this->entityTypeManager->getStorage($entityType)->create([$bundleType => $entityBundle]); - - $event = new LtiToolProviderProvisionCreateProvisionedEntityEvent($context, $entity); + if ($entityType && $entityBundle && isset($consumer_id) && !empty($consumer_id) && isset($context_id) && !empty($context_id) && isset($resource_link_id) && !empty($resource_link_id)) { + $provision = $this->getProvisionFromContext($context); + + if (!$provision) { + $provision = LtiToolProviderProvision::create(); + + if ($provision instanceof LtiToolProviderProvision) { + $provision->set('consumer_id', $consumer_id); + $provision->set('context_id', $context_id); + $provision->set('context_label', $context_label); + $provision->set('context_title', $context_title); + $provision->set('resource_link_id', $resource_link_id); + $provision->set('resource_link_title', $resource_link_title); + $provision->set('provision_type', $entityType); + $provision->set('provision_bundle', $entityBundle); + } + + $event = new LtiToolProviderProvisionCreateProvisionEvent($context, $provision); LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); if ($event->isCancelled()) { - throw new Exception($event->getMessage()); + throw new Exception($event->getMessage()); + } + + $provision = $event->getEntity(); + $provision->save(); + } + + if ($provision instanceof LtiToolProviderProvision) { + $entity = $provision->get('provision_id')->value ? $this->entityTypeManager + ->getStorage($provision->get('provision_type')->value) + ->load($provision->get('provision_id')->value) : NULL; + + if (!$entity) { + $entity = $this->createProvisionedEntity($context, $provision); } - $entity = $event->getEntity(); + $entity = $this->syncProvisionedEntity($context, $entity); + + $entity->save(); + $provision->set('provision_id', $entity->id()); + $provision->save(); return $entity; + } } - /** - * @param array $context - * @param EntityInterface $entity - * @return EntityInterface - * @throws Exception - */ - public function syncProvisionedEntity(array $context, EntityInterface $entity): EntityInterface - { - $entityDefaults = $this->config->get('entity_defaults'); - - if ($entityDefaults) { - foreach ($entityDefaults as $name => $entityDefault) { - if ($entity instanceof ContentEntityBase && isset($context[$entityDefault]) && !empty($context[$entityDefault])) { - $entity->set($name, $context[$entityDefault]); - } - } - } + throw new Exception('Unable to provision entity.'); + } + + /** + * @param array $context + * @param EntityInterface|LtiToolProviderProvision $provision + * @return EntityInterface + * @throws InvalidPluginDefinitionException + * @throws PluginNotFoundException + * @throws Exception + */ + public function createProvisionedEntity(array $context, EntityInterface $provision): EntityInterface { + $entityType = $provision->get('provision_type')->value; + $entityBundle = $provision->get('provision_bundle')->value; + + $bundleType = $this->entityTypeManager->getDefinition($entityType)->getKey('bundle'); + $entity = $this->entityTypeManager->getStorage($entityType)->create([$bundleType => $entityBundle]); + + $event = new LtiToolProviderProvisionCreateProvisionedEntityEvent($context, $entity); + LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); + + if ($event->isCancelled()) { + throw new Exception($event->getMessage()); + } - $event = new LtiToolProviderProvisionSyncProvisionedEntityEvent($context, $entity); - LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); + $entity = $event->getEntity(); - if ($event->isCancelled()) { - throw new Exception($event->getMessage()); - } + return $entity; + } - $entity = $event->getEntity(); + /** + * @param array $context + * @param EntityInterface $entity + * @return EntityInterface + * @throws Exception + */ + public function syncProvisionedEntity(array $context, EntityInterface $entity): EntityInterface { + $entityDefaults = []; + $lti_version = get_lti_version_by_launch_data($context); - return $entity; + if (($lti_version === 'v1p0')) { + $entityDefaults = $this->config->get('v1p0_entity_defaults'); + } + + if ($lti_version === 'v1p3') { + $entityDefaults = $this->config->get('v1p3_entity_defaults'); } - /** - * @param $context - * @return EntityInterface|null - * @throws InvalidPluginDefinitionException - * @throws PluginNotFoundException - */ - public function getProvisionFromContext($context): ?EntityInterface - { - $provision = $this->entityTypeManager->getStorage('lti_tool_provider_provision') - ->loadByProperties( - [ - 'consumer_id' => $context['consumer_id'], - 'context_id' => $context['context_id'], - 'resource_link_id' => $context['resource_link_id'], - ] - ); - - if (count($provision)) { - return reset($provision); + if ($entityDefaults) { + foreach ($entityDefaults as $name => $entityDefault) { + $context_data = $lti_version === 'v1p3' ? get_data_from_context_by_lti_attribute($context, $entityDefault) : $context[$entityDefault]; + + if ($entity instanceof ContentEntityBase && isset($context_data) && !empty($context_data)) { + $entity->set($name, $context_data); } + } + } + + $event = new LtiToolProviderProvisionSyncProvisionedEntityEvent($context, $entity); + LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); - return null; + if ($event->isCancelled()) { + throw new Exception($event->getMessage()); } - /** - * @param EntityInterface $entity - * @return EntityInterface|null - * @throws InvalidPluginDefinitionException - * @throws PluginNotFoundException - */ - public function getProvisionFromEntity(EntityInterface $entity): ?EntityInterface - { - $provision = $this->entityTypeManager->getStorage('lti_tool_provider_provision') - ->loadByProperties( - [ - 'provision_type' => $entity->getEntityTypeId(), - 'provision_bundle' => $entity->bundle(), - 'provision_id' => $entity->id(), - ] - ); - - if (count($provision)) { - return reset($provision); - } + $entity = $event->getEntity(); + + return $entity; + } + + /** + * @param $context + * @return EntityInterface|null + * @throws InvalidPluginDefinitionException + * @throws PluginNotFoundException + */ + public function getProvisionFromContext($context): ?EntityInterface { + $consumer_id = ''; + $context_id = ''; + $resource_link_id = ''; + $lti_version = get_lti_version_by_launch_data($context); + + if (($lti_version === 'v1p0')) { + $consumer_id = $context['consumer_id']; + $context_id = $context['context_id']; + $resource_link_id = $context['resource_link_id']; + } + + if ($lti_version === 'v1p3') { + $consumer_id = $context['aud']; + $context_id = $context[LTI_CLAIM_BASE_URL . 'context']['id']; + $resource_link_id = $context[LTI_CLAIM_BASE_URL . 'resource_link']['id']; + } - return null; + $provision = $this->entityTypeManager->getStorage('lti_tool_provider_provision') + ->loadByProperties( + [ + 'consumer_id' => $consumer_id, + 'context_id' => $context_id, + 'resource_link_id' => $resource_link_id, + ] + ); + + if (count($provision)) { + return reset($provision); } + + return NULL; + } + + /** + * @param EntityInterface $entity + * @return EntityInterface|null + * @throws InvalidPluginDefinitionException + * @throws PluginNotFoundException + */ + public function getProvisionFromEntity(EntityInterface $entity): ?EntityInterface { + $provision = $this->entityTypeManager->getStorage('lti_tool_provider_provision') + ->loadByProperties( + [ + 'provision_type' => $entity->getEntityTypeId(), + 'provision_bundle' => $entity->bundle(), + 'provision_id' => $entity->id(), + ] + ); + + if (count($provision)) { + return reset($provision); + } + + return NULL; + } + } diff --git a/modules/lti_tool_provider_roles/lti_tool_provider_roles.links.menu.yml b/modules/lti_tool_provider_roles/lti_tool_provider_roles.links.menu.yml index cd17c7a..418b328 100644 --- a/modules/lti_tool_provider_roles/lti_tool_provider_roles.links.menu.yml +++ b/modules/lti_tool_provider_roles/lti_tool_provider_roles.links.menu.yml @@ -2,5 +2,5 @@ lti_tool_provider.admin.roles: title: LTI role mapping parent: lti_tool_provider.admin description: Administer LTI role mapping. - route_name: lti_tool_provider.admin.roles + route_name: lti_tool_provider.admin.roles.lti_v1.0 weight: 2 diff --git a/modules/lti_tool_provider_roles/lti_tool_provider_roles.links.task.yml b/modules/lti_tool_provider_roles/lti_tool_provider_roles.links.task.yml new file mode 100644 index 0000000..33a5674 --- /dev/null +++ b/modules/lti_tool_provider_roles/lti_tool_provider_roles.links.task.yml @@ -0,0 +1,9 @@ +lti_tool_provider.admin.roles.lti_v1.0_settings_form: + title: 'LTI 1.0/1.1' + route_name: lti_tool_provider.admin.roles.lti_v1.0 + base_route: lti_tool_provider.admin.roles.lti_v1.0 + +lti_tool_provider.admin.roles.lti_v1.3_settings_form: + title: 'LTI 1.3' + route_name: lti_tool_provider.admin.roles.lti_v1.3 + base_route: lti_tool_provider.admin.roles.lti_v1.0 diff --git a/modules/lti_tool_provider_roles/lti_tool_provider_roles.routing.yml b/modules/lti_tool_provider_roles/lti_tool_provider_roles.routing.yml index a627c24..a8685bd 100644 --- a/modules/lti_tool_provider_roles/lti_tool_provider_roles.routing.yml +++ b/modules/lti_tool_provider_roles/lti_tool_provider_roles.routing.yml @@ -1,7 +1,15 @@ -lti_tool_provider.admin.roles: - path: /admin/config/lti-tool-provider/roles +lti_tool_provider.admin.roles.lti_v1.0: + path: /admin/config/lti-tool-provider/roles/v1p0 defaults: - _form: \Drupal\lti_tool_provider_roles\Form\LtiToolProviderRolesSettingsForm - _title: Mapped Roles + _form: \Drupal\lti_tool_provider_roles\Form\V1p0LtiToolProviderRolesSettingsForm + _title: Mapped Roles for LTI 1.0/1.1 + requirements: + _permission: administer lti_tool_provider module + +lti_tool_provider.admin.roles.lti_v1.3: + path: /admin/config/lti-tool-provider/roles/v1p3 + defaults: + _form: \Drupal\lti_tool_provider_roles\Form\V1p3LtiToolProviderRolesSettingsForm + _title: Mapped Roles for LTI 1.3 requirements: _permission: administer lti_tool_provider module diff --git a/modules/lti_tool_provider_roles/src/Event/LtiToolProviderRolesEvent.php b/modules/lti_tool_provider_roles/src/Event/LtiToolProviderRolesEvent.php index 32efdd4..de20778 100644 --- a/modules/lti_tool_provider_roles/src/Event/LtiToolProviderRolesEvent.php +++ b/modules/lti_tool_provider_roles/src/Event/LtiToolProviderRolesEvent.php @@ -5,60 +5,59 @@ namespace Drupal\lti_tool_provider_roles\Event; use Drupal\lti_tool_provider\LtiToolProviderEvent; use Drupal\user\UserInterface; -class LtiToolProviderRolesEvent extends LtiToolProviderEvent -{ - const EVENT_NAME = 'LTI_TOOL_PROVIDER_ROLES_EVENT'; +/** + * Implementation LtiToolProviderRolesEvent class. + */ +class LtiToolProviderRolesEvent extends LtiToolProviderEvent { + const EVENT_NAME = 'LTI_TOOL_PROVIDER_ROLES_EVENT'; + + /** + * @var array + */ + private $context; + + /** + * @var \Drupal\user\UserInterface + */ + private $user; + + /** + * LtiToolProviderRolesEvent constructor. + * + * @param array $context + * @param \Drupal\user\UserInterface $user + */ + public function __construct(array $context, UserInterface $user) { + $this->setContext($context); + $this->setUser($user); + } + + /** + * @return array + */ + public function getContext(): array { + return $this->context; + } + + /** + * @param array $context + */ + public function setContext(array $context) { + $this->context = $context; + } + + /** + * @return \Drupal\user\UserInterface + */ + public function getUser(): UserInterface { + return $this->user; + } + + /** + * @param \Drupal\user\UserInterface $user + */ + public function setUser(UserInterface $user): void { + $this->user = $user; + } - /** - * @var array - */ - private $context; - - /** - * @var UserInterface - */ - private $user; - - /** - * LtiToolProviderRolesEvent constructor. - * @param array $context - * @param UserInterface $user - */ - public function __construct(array $context, UserInterface $user) - { - $this->setContext($context); - $this->setUser($user); - } - - /** - * @return array - */ - public function getContext(): array - { - return $this->context; - } - - /** - * @param array $context - */ - public function setContext(array $context) - { - $this->context = $context; - } - - /** - * @return UserInterface - */ - public function getUser(): UserInterface - { - return $this->user; - } - - /** - * @param UserInterface $user - */ - public function setUser(UserInterface $user): void - { - $this->user = $user; - } } diff --git a/modules/lti_tool_provider_roles/src/EventSubscriber/LtiToolProviderRolesEventSubscriber.php b/modules/lti_tool_provider_roles/src/EventSubscriber/LtiToolProviderRolesEventSubscriber.php index 35f05a2..661bc93 100644 --- a/modules/lti_tool_provider_roles/src/EventSubscriber/LtiToolProviderRolesEventSubscriber.php +++ b/modules/lti_tool_provider_roles/src/EventSubscriber/LtiToolProviderRolesEventSubscriber.php @@ -11,84 +11,103 @@ use Exception; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -class LtiToolProviderRolesEventSubscriber implements EventSubscriberInterface -{ - /** - * @var ConfigFactoryInterface - */ - protected $configFactory; - - /** - * @var EventDispatcherInterface - */ - protected $eventDispatcher; - - /** - * LtiToolProviderRolesEventSubscriber constructor. - * @param ConfigFactoryInterface $configFactory - * @param EventDispatcherInterface $eventDispatcher - */ - public function __construct( - ConfigFactoryInterface $configFactory, - EventDispatcherInterface $eventDispatcher - ) { - $this->configFactory = $configFactory; - $this->eventDispatcher = $eventDispatcher; - } +/** + * Implementation LtiToolProviderRolesEventSubscriber class. + * + * @package Drupal\lti_tool_provider_roles\EventSubscriber + */ +class LtiToolProviderRolesEventSubscriber implements EventSubscriberInterface { - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - return [ - LtiToolProviderAuthenticatedEvent::EVENT_NAME => 'onAuthenticated', - ]; - } + /** + * @var ConfigFactoryInterface + */ + protected $configFactory; - /** - * @param LtiToolProviderAuthenticatedEvent $event - */ - public function onAuthenticated(LtiToolProviderAuthenticatedEvent $event) - { - $mapped_roles = Drupal::config('lti_tool_provider_roles.settings')->get('mapped_roles'); - $context = $event->getContext(); - $user = $event->getUser(); + /** + * @var EventDispatcherInterface + */ + protected $eventDispatcher; - $user_roles = user_roles(true); - $lti_roles = parse_roles($context['roles']); + /** + * LtiToolProviderRolesEventSubscriber constructor. + * + * @param ConfigFactoryInterface $configFactory + * @param EventDispatcherInterface $eventDispatcher + */ + public function __construct( + ConfigFactoryInterface $configFactory, + EventDispatcherInterface $eventDispatcher + ) { + $this->configFactory = $configFactory; + $this->eventDispatcher = $eventDispatcher; + } - if ($user->getDisplayName() === 'ltiuser') { - return; - } + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + LtiToolProviderAuthenticatedEvent::EVENT_NAME => 'onAuthenticated', + ]; + } - if (!$mapped_roles || !count($mapped_roles)) { - return; - } + /** + * Implementation onAuthenticated method. + * + * @param LtiToolProviderAuthenticatedEvent $event + */ + public function onAuthenticated(LtiToolProviderAuthenticatedEvent $event) { + $context = $event->getContext(); + $lti_roles = []; + $lti_version = get_lti_version_by_launch_data($context); - foreach ($mapped_roles as $user_role => $lti_role) { - if (array_key_exists($user_role, $user_roles)) { - if (in_array($lti_role, $lti_roles)) { - $user->addRole($user_role); - } - else { - $user->removeRole($user_role); - } - } - } + if (($lti_version === 'v1p0')) { + $mapped_roles = Drupal::config('lti_tool_provider_roles.settings')->get('v1p0_mapped_roles'); + $lti_roles = parse_roles($context['roles']); + } + elseif ($lti_version === 'v1p3') { + $mapped_roles = Drupal::config('lti_tool_provider_roles.settings')->get('v1p3_mapped_roles'); + $lti_roles = $context['https://purl.imsglobal.org/spec/lti/claim/roles']; + } + else { + $mapped_roles = Drupal::config('lti_tool_provider_roles.settings')->get('mapped_roles'); + } - try { - $rolesEvent = new LtiToolProviderRolesEvent($context, $user); - LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $rolesEvent); + $user = $event->getUser(); + $user_roles = user_roles(TRUE); - if ($rolesEvent->isCancelled()) { - throw new Exception($event->getMessage()); - } + if ($user->getDisplayName() === 'ltiuser') { + return; + } - $user->save(); + if (!$mapped_roles || !count($mapped_roles)) { + return; + } + + foreach ($mapped_roles as $user_role => $lti_role) { + if (array_key_exists($user_role, $user_roles)) { + if (in_array($lti_role, $lti_roles)) { + $user->addRole($user_role); } - catch (Exception $e) { - Drupal::logger('lti_tool_provider_roles')->error($e->getMessage()); + else { + $user->removeRole($user_role); } + } + } + + try { + $rolesEvent = new LtiToolProviderRolesEvent($context, $user); + LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $rolesEvent); + + if ($rolesEvent->isCancelled()) { + throw new Exception($event->getMessage()); + } + + $user->save(); } + catch (Exception $e) { + Drupal::logger('lti_tool_provider_roles')->error($e->getMessage()); + } + } + } diff --git a/modules/lti_tool_provider_roles/src/Form/LtiToolProviderRolesSettingsForm.php b/modules/lti_tool_provider_roles/src/Form/LtiToolProviderRolesSettingsForm.php deleted file mode 100644 index 5a5b564..0000000 --- a/modules/lti_tool_provider_roles/src/Form/LtiToolProviderRolesSettingsForm.php +++ /dev/null @@ -1,82 +0,0 @@ -config('lti_tool_provider_roles.settings'); - $mapped_roles = $settings->get('mapped_roles'); - - $lti_roles = $this->config('lti_tool_provider.settings')->get('lti_roles'); - - $form['mapped_roles'] = [ - '#type' => 'table', - '#tree' => true, - '#caption' => t( - 'This page allows you to map LTI roles to Drupal user roles. This is applied every time a user logs in via LTI. Please note that if roles are mapped and they are not present on the LMS, they will be removed from the Drupal user. Please be careful when setting this for the authenticated user role.' - ), - '#header' => [t('User Role'), t('LTI Role')], - ]; - - foreach (user_roles(true) as $key => $user_role) { - $form['mapped_roles'][$key] = [ - 'user_role' => [ - '#type' => 'item', - '#title' => $user_role->label(), - ], - 'lti_role' => [ - '#type' => 'select', - '#required' => false, - '#empty_option' => t('None'), - '#empty_value' => true, - '#default_value' => $mapped_roles[$key], - '#options' => array_combine($lti_roles, $lti_roles), - ], - ]; - } - - return parent::buildForm($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) - { - $settings = $this->config('lti_tool_provider_roles.settings'); - $lti_roles = $this->config('lti_tool_provider.settings')->get('lti_roles'); - - $mapped_roles = []; - foreach ($form_state->getValue('mapped_roles') as $key => $value) { - if (in_array($value['lti_role'], $lti_roles)) { - $mapped_roles[$key] = $value['lti_role']; - } - } - - $settings->set('mapped_roles', $mapped_roles)->save(); - } -} diff --git a/modules/lti_tool_provider_roles/src/Form/V1p0LtiToolProviderRolesSettingsForm.php b/modules/lti_tool_provider_roles/src/Form/V1p0LtiToolProviderRolesSettingsForm.php new file mode 100644 index 0000000..b80b631 --- /dev/null +++ b/modules/lti_tool_provider_roles/src/Form/V1p0LtiToolProviderRolesSettingsForm.php @@ -0,0 +1,84 @@ +config('lti_tool_provider_roles.settings'); + $mapped_roles = $settings->get('v1p0_mapped_roles'); + + $lti_roles = $this->config('lti_tool_provider.settings')->get('lti_roles_v1p0'); + + $form['mapped_roles'] = [ + '#type' => 'table', + '#tree' => TRUE, + '#caption' => t( + 'This page allows you to map LTI roles to Drupal user roles. This is applied every time a user logs in via LTI. Please note that if roles are mapped and they are not present on the LMS, they will be removed from the Drupal user. Please be careful when setting this for the authenticated user role.' + ), + '#header' => [t('User Role'), t('LTI Role')], + ]; + + foreach (user_roles(TRUE) as $key => $user_role) { + $form['mapped_roles'][$key] = [ + 'user_role' => [ + '#type' => 'item', + '#title' => $user_role->label(), + ], + 'lti_role' => [ + '#type' => 'select', + '#required' => FALSE, + '#empty_option' => t('None'), + '#empty_value' => TRUE, + '#default_value' => isset($mapped_roles[$key]) ? $mapped_roles[$key] : '', + '#options' => array_combine($lti_roles, $lti_roles), + ], + ]; + } + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $settings = $this->config('lti_tool_provider_roles.settings'); + $lti_roles = $this->config('lti_tool_provider.settings')->get('lti_roles_v1p0'); + + $mapped_roles = []; + foreach ($form_state->getValue('mapped_roles') as $key => $value) { + if (in_array($value['lti_role'], $lti_roles)) { + $mapped_roles[$key] = $value['lti_role']; + } + } + + $settings->set('v1p0_mapped_roles', $mapped_roles)->save(); + } + +} diff --git a/modules/lti_tool_provider_roles/src/Form/V1p3LtiToolProviderRolesSettingsForm.php b/modules/lti_tool_provider_roles/src/Form/V1p3LtiToolProviderRolesSettingsForm.php new file mode 100644 index 0000000..5a3d9fb --- /dev/null +++ b/modules/lti_tool_provider_roles/src/Form/V1p3LtiToolProviderRolesSettingsForm.php @@ -0,0 +1,88 @@ +config('lti_tool_provider_roles.settings'); + $mapped_roles = $settings->get('v1p3_mapped_roles'); + + $lti_roles = $this->config('lti_tool_provider.settings')->get('lti_roles_v1p3'); + + $form['mapped_roles'] = [ + '#type' => 'table', + '#tree' => TRUE, + '#caption' => t( + 'This page allows you to map LTI roles to Drupal user roles. This is applied every time a user logs in via LTI. Please note that if roles are mapped and they are not present on the LMS, they will be removed from the Drupal user. Please be careful when setting this for the authenticated user role.' + ), + '#header' => [t('User Role'), t('LTI Role')], + ]; + + foreach (user_roles(TRUE) as $key => $user_role) { + /* Exclude authenticated role because Anonymous or authenticated role + * ID must not be assigned manually. */ + if ($key != 'authenticated') { + $form['mapped_roles'][$key] = [ + 'user_role' => [ + '#type' => 'item', + '#title' => $user_role->label(), + ], + 'lti_role' => [ + '#type' => 'select', + '#required' => FALSE, + '#empty_option' => t('None'), + '#empty_value' => TRUE, + '#default_value' => isset($mapped_roles[$key]) ? $mapped_roles[$key] : '', + '#options' => array_combine($lti_roles, $lti_roles), + ], + ]; + } + } + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $settings = $this->config('lti_tool_provider_roles.settings'); + $lti_roles = $this->config('lti_tool_provider.settings')->get('lti_roles_v1p3'); + + $mapped_roles = []; + foreach ($form_state->getValue('mapped_roles') as $key => $value) { + if (in_array($value['lti_role'], $lti_roles)) { + $mapped_roles[$key] = $value['lti_role']; + } + } + + $settings->set('v1p3_mapped_roles', $mapped_roles)->save(); + } + +} diff --git a/src/Authentication/Provider/LTIToolProvider.php b/src/Authentication/Provider/LTIToolProvider.php index bd3dc81..cec81ed 100644 --- a/src/Authentication/Provider/LTIToolProvider.php +++ b/src/Authentication/Provider/LTIToolProvider.php @@ -8,6 +8,7 @@ use Drupal\Core\Authentication\AuthenticationProviderInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelFactoryInterface; +use Drupal\lti_tool_provider\Services\LTIServiceInterface; use Drupal\Core\Url; use Drupal\lti_tool_provider\Entity\LtiToolProviderConsumer; use Drupal\lti_tool_provider\Event\LtiToolProviderAuthenticatedEvent; @@ -23,351 +24,551 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; /** - * Oauth authentication provider for LTI Tool Provider. + * Authentication provider for LTI Tool Provider. */ -class LTIToolProvider implements AuthenticationProviderInterface -{ - /** - * The configuration factory. - * - * @var ConfigFactoryInterface - */ - protected $configFactory; - - /** - * The entity type manager. - * - * @var EntityTypeManagerInterface - */ - protected $entityTypeManager; - - /** - * A logger instance. - * - * @var LoggerChannelFactoryInterface - */ - protected $loggerFactory; - - /** - * The event dispatcher. - * - * @var EventDispatcherInterface - */ - protected $eventDispatcher; - - /** - * The consumer entity matching the LTI request. - * - * @var LtiToolProviderConsumer - */ - protected $consumerEntity; - - /** - * The LTI context, i.e. the request parameters. - * - * @var array - */ - protected $context; - - /** - * The LTI provisioned user. - * - * @var UserInterface - * - */ - protected $user; - - /** - * Constructs a HTTP basic authentication provider object. - * - * @param ConfigFactoryInterface $config_factory - * The configuration factory. - * @param EntityTypeManagerInterface $entity_type_manager - * The entity manager. - * @param LoggerChannelFactoryInterface $logger_factory - * A logger instance. - * @param EventDispatcherInterface $eventDispatcher - * The event dispatcher. - */ - public function __construct( - ConfigFactoryInterface $config_factory, - EntityTypeManagerInterface $entity_type_manager, - LoggerChannelFactoryInterface $logger_factory, - EventDispatcherInterface $eventDispatcher - ) { - $this->configFactory = $config_factory; - $this->entityTypeManager = $entity_type_manager; - $this->loggerFactory = $logger_factory->get('lti_tool_provider'); - $this->eventDispatcher = $eventDispatcher; - } - - /** - * {@inheritdoc} - * - * @see https://www.imsglobal.org/wiki/step-1-lti-launch-request - */ - public function applies(Request $request): bool - { - $lti_message_type = $request->request->get('lti_message_type'); - $lti_version = $request->request->get('lti_version'); - $oauth_consumer_key = $request->request->get('oauth_consumer_key'); - $resource_link_id = $request->request->get('resource_link_id'); - - if (!$request->isMethod('POST')) { - return false; +class LTIToolProvider implements AuthenticationProviderInterface { + + /** + * The configuration factory. + * + * @var ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The entity type manager. + * + * @var EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * A logger instance. + * + * @var LoggerChannelFactoryInterface + */ + protected $loggerFactory; + + /** + * The event dispatcher. + * + * @var EventDispatcherInterface + */ + protected $eventDispatcher; + + /** + * The consumer entity matching the LTI request. + * + * @var LtiToolProviderConsumer + */ + protected $consumerEntity; + + /** + * The LTI context, i.e. the request parameters. + * + * @var array + */ + protected $context; + + /** + * The LTI provisioned user. + * + * @var UserInterface + * + */ + protected $user; + + /** + * The LTI service instance + * + * @var LTIServiceInterface + */ + protected $ltiService; + + /** + * Constructs a HTTP basic authentication provider object. + * + * @param ConfigFactoryInterface $config_factory + * The configuration factory. + * @param EntityTypeManagerInterface $entity_type_manager + * The entity manager. + * @param LoggerChannelFactoryInterface $logger_factory + * A logger instance. + * @param EventDispatcherInterface $eventDispatcher + * The event dispatcher. + * @param LTIServiceInterface $ltiService + */ + public function __construct( + ConfigFactoryInterface $config_factory, + EntityTypeManagerInterface $entity_type_manager, + LoggerChannelFactoryInterface $logger_factory, + EventDispatcherInterface $eventDispatcher, + LTIServiceInterface $ltiService + ) { + $this->configFactory = $config_factory; + $this->entityTypeManager = $entity_type_manager; + $this->loggerFactory = $logger_factory->get('lti_tool_provider'); + $this->eventDispatcher = $eventDispatcher; + $this->ltiService = $ltiService; + } + + /** + * {@inheritdoc} + * + * @see https://www.imsglobal.org/wiki/step-1-lti-launch-request + */ + public function applies(Request $request): bool { + // This is a LTI request parameters validation before Login request. + return $this->ltiService->pre_login_validate($request); + } + + /** + * {@inheritdoc} + */ + public function authenticate(Request $request) { + try { + // LTI v1.0. + if ($this->is_v1p0_login_lti_request($request)) { + $this->context = $request->request->all(); + + $event = new LtiToolProviderLaunchEvent($this->context); + LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); + + if ($event->isCancelled()) { + throw new Exception($event->getMessage()); } - if ($lti_message_type !== 'basic-lti-launch-request') { - return false; - } + $this->context = $event->getContext(); - if (!in_array($lti_version, ['LTI-1p0', 'LTI-1p2'])) { - return false; - } + $this->validateOauthRequest(); + $this->provisionUser(); - if (empty($oauth_consumer_key)) { - return false; + $event = new LtiToolProviderAuthenticatedEvent($this->context, $this->user); + LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); + + if ($event->isCancelled()) { + throw new Exception($event->getMessage()); } - if (empty($resource_link_id)) { - return false; + $this->context = $event->getContext(); + $this->user = $event->getUser(); + + $this->userLoginFinalize(); + + $this->context['consumer_id'] = $this->consumerEntity->id(); + $this->context['consumer_label'] = $this->consumerEntity->label(); + + $session = $request->getSession(); + $session->set('lti_tool_provider_context', $this->context); + $session->remove('lti_tool_provider_launch'); + + return $this->user; + } + // LTI v1.3. + elseif ($this->is_v1p3_login_lti_request($request)) { + // This is a LTI Login request. + $this->ltiService->login(); + } + elseif ($this->is_v1p3_launch_lti_request($request)) { + // This is a LTI launch request. + $launch = $this->ltiService->validate(); + $this->context = $launch->get_launch_data(); + + $event = new LtiToolProviderLaunchEvent($this->context); + LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); + + if ($event->isCancelled()) { + throw new Exception($event->getMessage()); } - return true; - } + $this->context = $event->getContext(); - /** - * {@inheritdoc} - */ - public function authenticate(Request $request) - { - try { - $this->context = $request->request->all(); + $this->v1p3ConsumerHandler(); + $this->v1p3TimestampNonceHandler(); + $this->provisionUser(); - $event = new LtiToolProviderLaunchEvent($this->context); - LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); + $this->context['consumer_id'] = $this->consumerEntity->id(); + $this->context['consumer_label'] = $this->consumerEntity->label(); - if ($event->isCancelled()) { - throw new Exception($event->getMessage()); - } + $event = new LtiToolProviderAuthenticatedEvent($this->context, $this->user); + LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); - $this->context = $event->getContext(); + if ($event->isCancelled()) { + throw new Exception($event->getMessage()); + } - $this->validateOauthRequest(); - $this->provisionUser(); + $this->context = $event->getContext(); + $this->user = $event->getUser(); - $event = new LtiToolProviderAuthenticatedEvent($this->context, $this->user); - LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); + $this->userLoginFinalize(); - if ($event->isCancelled()) { - throw new Exception($event->getMessage()); - } + $session = $request->getSession(); + $session->set('lti_tool_provider_context', $this->context); + $session->set('lti_tool_provider_launch', $launch); - $this->context = $event->getContext(); - $this->user = $event->getUser(); + return $this->user; + } + elseif ($this->is_return_lti_request($request)) { + $session = $request->getSession(); + $this->context = $session->get('lti_tool_provider_context'); + } + } + catch (Exception $e) { + $this->loggerFactory->warning($e->getMessage()); + $this->sendLtiError($e->getMessage()); - $this->userLoginFinalize(); + return NULL; + } + } + + /** + * Validate the OAuth request. + */ + private function validateOauthRequest() { + $provider = new OAuthProvider(["oauth_signature_method" => OAUTH_SIG_METHOD_HMACSHA1]); + $provider->consumerHandler([$this, 'v1p0ConsumerHandler']); + $provider->timestampNonceHandler([$this, 'v1p0TimestampNonceHandler']); + $provider->isRequestTokenEndpoint(FALSE); + $provider->is2LeggedEndpoint(TRUE); + $provider->checkOAuthRequest(); + } + + /** + * In order to be sure it's login LTI v1.0 request. + * + * @param Request $request + * + * @return bool + */ + private function is_v1p0_login_lti_request(Request $request) { + $parameters = $request->request->all(); + + $is_lti_message_type = array_key_exists('lti_message_type', $parameters); + $is_lti_version = array_key_exists('lti_version', $parameters); + $is_oauth_consumer_key = array_key_exists('oauth_consumer_key', $parameters); + $is_resource_link_id = array_key_exists('resource_link_id', $parameters); + + if ($is_lti_message_type && $is_lti_version && $is_oauth_consumer_key && $is_resource_link_id) { + return TRUE; + } - $this->context['consumer_id'] = $this->consumerEntity->id(); - $this->context['consumer_label'] = $this->consumerEntity->label(); + return FALSE; + } + + /** + * In order to be sure it's login LTI v1.3 request. + * + * @param Request $request + * + * @return bool + */ + private function is_v1p3_login_lti_request(Request $request) { + $parameters = $request->request->all(); + + $is_iss = array_key_exists('iss', $parameters); + $is_target_link_uri = array_key_exists('target_link_uri', $parameters); + $is_login_hint = array_key_exists('login_hint', $parameters); + $is_lti_message_hint = array_key_exists('lti_message_hint', $parameters); + + if ($is_iss && $is_target_link_uri && $is_login_hint && $is_lti_message_hint) { + return TRUE; + } - $session = $request->getSession(); - $session->set('lti_tool_provider_context', $this->context); + return FALSE; + } - return $this->user; - } - catch (Exception $e) { - $this->loggerFactory->warning($e->getMessage()); - $this->sendLtiError($e->getMessage()); + /** + * In order to be sure it's launch LTI request. + * + * @param Request $request + * + * @return bool + */ + private function is_v1p3_launch_lti_request(Request $request) { + $parameters = $request->request->all(); - return null; - } + $is_id_token = array_key_exists('id_token', $parameters); + $is_state = array_key_exists('state', $parameters); + + if ($is_id_token && $is_state) { + return TRUE; } - /** - * Validate the OAuth request. - */ - private function validateOauthRequest() - { - $provider = new OAuthProvider(["oauth_signature_method" => OAUTH_SIG_METHOD_HMACSHA1]); - $provider->consumerHandler([$this, 'consumerHandler']); - $provider->timestampNonceHandler([$this, 'timestampNonceHandler']); - $provider->isRequestTokenEndpoint(false); - $provider->is2LeggedEndpoint(true); - $provider->checkOAuthRequest(); + return FALSE; + } + + /** + * In order to be sure it's return LTI request. + * + * @param Request $request + * @return bool + */ + private function is_return_lti_request(Request $request) { + return $request->getRequestUri() == '/lti/return'; + } + + /** + * Looks up the consumer entity that matches the consumer key. + * + * @param $provider + * + * @return int + * - OAUTH_OK if validated. + * - OAUTH_CONSUMER_KEY_UNKNOWN if not. + */ + public function v1p0ConsumerHandler($provider): int { + try { + $ids = $this->entityTypeManager->getStorage('lti_tool_provider_consumer') + ->getQuery() + ->condition('consumer_key', $provider->consumer_key, '=') + ->execute(); + + if (!count($ids)) { + return OAUTH_CONSUMER_KEY_UNKNOWN; + } + + $this->consumerEntity = $this->entityTypeManager->getStorage('lti_tool_provider_consumer')->load(key($ids)); + $provider->consumer_secret = $this->consumerEntity->get('consumer_secret')->getValue()[0]['value']; + } + catch (InvalidPluginDefinitionException | PluginNotFoundException $e) { + return OAUTH_CONSUMER_KEY_UNKNOWN; } - /** - * Looks up the consumer entity that matches the consumer key. - * - * @param $provider - * @return int - * - OAUTH_OK if validated. - * - OAUTH_CONSUMER_KEY_UNKNOWN if not. - */ - public function consumerHandler($provider): int - { - try { - $ids = $this->entityTypeManager->getStorage('lti_tool_provider_consumer') - ->getQuery() - ->condition('consumer_key', $provider->consumer_key, '=') - ->execute(); - - if (!count($ids)) { - return OAUTH_CONSUMER_KEY_UNKNOWN; - } - - $this->consumerEntity = $this->entityTypeManager->getStorage('lti_tool_provider_consumer')->load(key($ids)); - $provider->consumer_secret = $this->consumerEntity->get('consumer_secret')->getValue()[0]['value']; - } - catch (InvalidPluginDefinitionException | PluginNotFoundException $e) { - return OAUTH_CONSUMER_KEY_UNKNOWN; - } + return OAUTH_OK; + } + + /** + * Looks up the consumer entity that matches the consumer client id. + * + * @throws Exception + */ + public function v1p3ConsumerHandler() { + try { + $ids = $this->entityTypeManager->getStorage('lti_tool_provider_consumer') + ->getQuery() + ->condition('client_id', $this->context['aud'], '=') + ->execute(); + + if (!count($ids)) { + throw new Exception("CONSUMER KEY UNKNOWN"); + } + + $this->consumerEntity = $this->entityTypeManager->getStorage('lti_tool_provider_consumer')->load(key($ids)); + } + catch (InvalidPluginDefinitionException | PluginNotFoundException $e) { + throw new Exception("CONSUMER KEY UNKNOWN"); + } + } + + /** + * Validate nonce. + * + * @param $provider + * + * @return int + * - OAUTH_OK if validated. + * - OAUTH_BAD_TIMESTAMP if timestamp too old. + * - OAUTH_BAD_NONCE if nonce has been used. + */ + public function v1p0TimestampNonceHandler($provider): int { + // Verify timestamp has been set. + if (!isset($provider->timestamp)) { + return OAUTH_BAD_TIMESTAMP; + } - return OAUTH_OK; + // Verify nonce timestamp is not older than now - nonce interval. + if ($provider->timestamp < (time() - LTI_TOOL_PROVIDER_NONCE_INTERVAL)) { + return OAUTH_BAD_TIMESTAMP; } - /** - * Validate nonce. - * - * @param $provider - * @return int - * - OAUTH_OK if validated. - * - OAUTH_BAD_TIMESTAMP if timestamp too old. - * - OAUTH_BAD_NONCE if nonce has been used. - */ - public function timestampNonceHandler($provider): int - { - // Verify timestamp has been set. - if (!isset($provider->timestamp)) { - return OAUTH_BAD_TIMESTAMP; - } + // Verify nonce timestamp is not newer than now + nonce interval. + if ($provider->timestamp > (time() + LTI_TOOL_PROVIDER_NONCE_INTERVAL)) { + return OAUTH_BAD_TIMESTAMP; + } - // Verify nonce timestamp is not older than now - nonce interval. - if ($provider->timestamp < (time() - LTI_TOOL_PROVIDER_NONCE_INTERVAL)) { - return OAUTH_BAD_TIMESTAMP; - } + // Verify nonce and consumer_key has been set. + if (!isset($provider->nonce) || !isset($provider->consumer_key)) { + return OAUTH_BAD_NONCE; + } - // Verify nonce timestamp is not newer than now + nonce interval. - if ($provider->timestamp > (time() + LTI_TOOL_PROVIDER_NONCE_INTERVAL)) { - return OAUTH_BAD_TIMESTAMP; - } + try { + $storage = $this->entityTypeManager->getStorage('lti_tool_provider_nonce'); + + // Verify that current nonce is not a duplicate. + $nonce_exists = $storage->getQuery()->condition('nonce', $provider->nonce, '=')->execute(); + if (count($nonce_exists)) { + return OAUTH_BAD_NONCE; + } + + // Store nonce in database. + $storage->create( + [ + 'nonce' => $provider->nonce, + 'consumer_key' => $provider->consumer_key, + 'timestamp' => $provider->timestamp, + ] + )->save(); + } + catch (Exception $e) { + $this->loggerFactory->warning($e->getMessage()); - // Verify nonce and consumer_key has been set. - if (!isset($provider->nonce) || !isset($provider->consumer_key)) { - return OAUTH_BAD_NONCE; - } + return OAUTH_BAD_NONCE; + } - try { - $storage = $this->entityTypeManager->getStorage('lti_tool_provider_nonce'); - - // Verify that current nonce is not a duplicate. - $nonce_exists = $storage->getQuery()->condition('nonce', $provider->nonce, '=')->execute(); - if (count($nonce_exists)) { - return OAUTH_BAD_NONCE; - } - - // Store nonce in database. - $storage->create( - [ - 'nonce' => $provider->nonce, - 'consumer_key' => $provider->consumer_key, - 'timestamp' => $provider->timestamp, - ] - )->save(); - } - catch (Exception $e) { - $this->loggerFactory->warning($e->getMessage()); + return OAUTH_OK; + } + + /** + * Validate nonce. + * + * @throws InvalidPluginDefinitionException + * @throws PluginNotFoundException + * @throws \Drupal\Core\Entity\EntityStorageException + */ + public function v1p3TimestampNonceHandler() { + $issue_at = $this->context['iat']; + $nonce = $this->context['nonce']; + $client_id = $this->context['aud']; + + // Verify timestamp has been set. + if (!isset($issue_at)) { + throw new Exception("ISSUE BAD TIMESTAMP"); + } - return OAUTH_BAD_NONCE; - } + // Verify nonce timestamp is not older than now - nonce interval. + if ($issue_at < (time() - LTI_TOOL_PROVIDER_NONCE_INTERVAL)) { + throw new Exception("ISSUE BAD TIMESTAMP"); + } - return OAUTH_OK; + // Verify nonce timestamp is not newer than now + nonce interval. + if ($issue_at > (time() + LTI_TOOL_PROVIDER_NONCE_INTERVAL)) { + throw new Exception("ISSUE BAD TIMESTAMP"); } - /** - * Provision a user that matches the LTI request context info. - * - * @throws Exception - */ - protected function provisionUser() - { - try { - $name = 'ltiuser'; - $mail = 'ltiuser@invalid'; - - $name_param = $this->consumerEntity->get('name')->getValue()[0]['value']; - if (isset($this->context[$name_param]) && !empty($this->context[$name_param])) { - $name = $this->context[$name_param]; - } - - $mail_param = $this->consumerEntity->get('mail')->getValue()[0]['value']; - if (isset($this->context[$mail_param]) && !empty($this->context[$mail_param])) { - $mail = $this->context[$mail_param]; - } - - if ($users = $this->entityTypeManager->getStorage('user')->loadByProperties(['name' => $name, 'status' => 1])) { - $this->user = reset($users); - } - elseif ($users = $this->entityTypeManager->getStorage('user')->loadByProperties(['mail' => $mail, 'status' => 1])) { - $this->user = reset($users); - } - else { - $this->user = User::create(); - $this->user->setUsername($name); - $this->user->setEmail($mail); - $this->user->setPassword(user_password()); - $this->user->enforceIsNew(); - $this->user->activate(); - - $event = new LtiToolProviderProvisionUserEvent($this->context, $this->user); - LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); - - if ($event->isCancelled()) { - throw new Exception($event->getMessage()); - } - - $this->context = $event->getContext(); - $this->user = $event->getUser(); - - $this->user->save(); - } - } - catch (Exception $e) { - $this->loggerFactory->warning($e->getMessage()); - $this->sendLtiError($e->getMessage()); - } + // Verify nonce and consumer client id has been set. + if (!isset($nonce) || !isset($client_id)) { + throw new Exception("BAD NONCE"); } - /** - * Finalizes the user login. - */ - protected function userLoginFinalize() - { - user_login_finalize($this->user); + $storage = $this->entityTypeManager->getStorage('lti_tool_provider_nonce'); + + // Verify that current nonce is not a duplicate. + $nonce_exists = $storage->getQuery()->condition('nonce', $nonce, '=')->execute(); + if (count($nonce_exists)) { + throw new Exception("BAD NONCE"); } - /** - * Send an error back to the LMS. - * - * @param string $message - * The error message to send. - */ - protected function sendLtiError(string $message) - { - if (isset($this->context['launch_presentation_return_url']) && !empty($this->context['launch_presentation_return_url'])) { - $url = Url::fromUri($this->context['launch_presentation_return_url']) - ->setOption( - 'query', - [ - 'lti_errormsg' => $message, - ] - ) - ->setAbsolute(true) - ->toString(); - - $response = new RedirectResponse($url); - $response->send(); + // Store nonce in database. + $storage->create( + [ + 'nonce' => $nonce, + 'client_id' => $client_id, + 'timestamp' => $issue_at, + ] + )->save(); + } + + /** + * Provision a user that matches the LTI request context info. + * + * @throws Exception + */ + protected function provisionUser() { + try { + $name = 'ltiuser'; + $mail = 'ltiuser@invalid'; + + $name_param = $this->consumerEntity->get('name')->getValue()[0]['value']; + if (isset($this->context[$name_param]) && !empty($this->context[$name_param])) { + $name = $this->context[$name_param]; + } + + $mail_param = $this->consumerEntity->get('mail')->getValue()[0]['value']; + if (isset($this->context[$mail_param]) && !empty($this->context[$mail_param])) { + $mail = $this->context[$mail_param]; + } + + if ($users = $this->entityTypeManager + ->getStorage('user') + ->loadByProperties(['name' => $name, 'status' => 1]) + ) { + $this->user = reset($users); + } + elseif ($users = $this->entityTypeManager + ->getStorage('user') + ->loadByProperties(['mail' => $mail, 'status' => 1]) + ) { + $this->user = reset($users); + } + else { + $this->user = User::create(); + $this->user->setUsername($name); + $this->user->setEmail($mail); + $this->user->setPassword(user_password()); + $this->user->enforceIsNew(); + $this->user->activate(); + + $event = new LtiToolProviderProvisionUserEvent($this->context, $this->user); + LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); + + if ($event->isCancelled()) { + throw new Exception($event->getMessage()); } + + $this->context = $event->getContext(); + $this->user = $event->getUser(); + + $this->user->save(); + } + } + catch (Exception $e) { + $this->loggerFactory->warning($e->getMessage()); + $this->sendLtiError($e->getMessage()); } + } + + /** + * Finalizes the user login. + */ + protected function userLoginFinalize() { + user_login_finalize($this->user); + } + + /** + * Send an error back to the LMS. + * + * @param string $message + * The error message to send. + */ + protected function sendLtiError(string $message) { + $return_url = NULL; + $v1p0_return_url = $this->context['launch_presentation_return_url']; + $v1p3_return_url = $this->context[LTI_CLAIM_BASE_URL . 'launch_presentation']['return_url']; + + if (isset($v1p3_return_url) && !empty($v1p0_return_url)) { + $return_url = $v1p0_return_url; + } + elseif (isset($v1p3_return_url) && !empty($v1p3_return_url)) { + $return_url = $v1p3_return_url; + } + + if (isset($return_url)) { + $url = Url::fromUri($return_url) + ->setOption( + 'query', + [ + 'lti_errormsg' => $message, + ] + ) + ->setAbsolute(TRUE) + ->toString(); + + $response = new RedirectResponse($url); + $response->send(); + } + else { + \Drupal::messenger()->addError($message); + } + } + } diff --git a/src/Controller/LTIToolProviderController.php b/src/Controller/LTIToolProviderController.php index 7e024c8..c40996f 100644 --- a/src/Controller/LTIToolProviderController.php +++ b/src/Controller/LTIToolProviderController.php @@ -13,9 +13,11 @@ use Drupal\Core\Routing\TrustedRedirectResponse; use Drupal\lti_tool_provider\Event\LtiToolProviderLaunchRedirectEvent; use Drupal\lti_tool_provider\Event\LtiToolProviderReturnEvent; use Drupal\lti_tool_provider\LtiToolProviderEvent; +use Drupal\lti_tool_provider\Services\LTIServiceInterface; use Exception; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; @@ -23,243 +25,311 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface; /** * Returns responses for lti_tool_provider module routes. */ -class LTIToolProviderController extends ControllerBase -{ - /** - * The configuration factory. - * - * @var ConfigFactoryInterface - */ - protected $configFactory; - - /** - * A logger instance. - * - * @var LoggerChannelFactoryInterface - */ - protected $loggerFactory; - - /** - * The event dispatcher. - * - * @var EventDispatcherInterface - */ - protected $eventDispatcher; - - /** - * The page cache kill switch. - * - * @var ResponsePolicyInterface|KillSwitch - */ - protected $killSwitch; - - /** - * The request. - * - * @var Request - */ - protected $request; - - /** - * The request session. - * - * @var SessionInterface - */ - protected $session; - - /** - * The LTI context. - * - * @var mixed - */ - protected $context; - - /** - * Optional destination. - * - * @var string - */ - protected $destination; - - /** - * Constructs a HTTP basic authentication provider object. - * - * @param ConfigFactoryInterface $configFactory - * The configuration factory. - * @param LoggerChannelFactoryInterface $loggerFactory - * A logger instance. - * @param EventDispatcherInterface $eventDispatcher - * The event dispatcher. - * @param ResponsePolicyInterface $killSwitch - * The page cache kill switch. - * @param Request $request - * The request. - * @param SessionInterface $session - * The request session. - * @param mixed $context - * The LTI context. - * @param string | null $destination - * Optional destination. - */ - public function __construct( - ConfigFactoryInterface $configFactory, - LoggerChannelFactoryInterface $loggerFactory, - EventDispatcherInterface $eventDispatcher, - ResponsePolicyInterface $killSwitch, - Request $request, - SessionInterface $session, +class LTIToolProviderController extends ControllerBase { + + /** + * The LTI service. + * @var LTIServiceInterface + */ + protected $ltiService; + + /** + * The configuration factory. + * + * @var ConfigFactoryInterface + */ + protected $configFactory; + + /** + * A logger instance. + * + * @var LoggerChannelFactoryInterface + */ + protected $loggerFactory; + + /** + * The event dispatcher. + * + * @var EventDispatcherInterface + */ + protected $eventDispatcher; + + /** + * The page cache kill switch. + * + * @var ResponsePolicyInterface|KillSwitch + */ + protected $killSwitch; + + /** + * The request. + * + * @var Request + */ + protected $request; + + /** + * The request session. + * + * @var SessionInterface + */ + protected $session; + + /** + * The LTI context. + * + * @var mixed + */ + protected $context; + + /** + * Optional destination. + * + * @var string + */ + protected $destination; + + /** + * Constructs a HTTP basic authentication provider object. + * + * @param LTIServiceInterface $ltiService + * The LTI service. + * @param ConfigFactoryInterface $configFactory + * The configuration factory. + * @param LoggerChannelFactoryInterface $loggerFactory + * A logger instance. + * @param EventDispatcherInterface $eventDispatcher + * The event dispatcher. + * @param ResponsePolicyInterface $killSwitch + * The page cache kill switch. + * @param Request $request + * The request. + * @param SessionInterface $session + * The request session. + * @param mixed $context + * The LTI context. + * @param string | null $destination + * Optional destination. + */ + public function __construct( + LTIServiceInterface $ltiService, + ConfigFactoryInterface $configFactory, + LoggerChannelFactoryInterface $loggerFactory, + EventDispatcherInterface $eventDispatcher, + ResponsePolicyInterface $killSwitch, + Request $request, + SessionInterface $session, + $context, + ?string $destination + ) { + $this->ltiService = $ltiService; + $this->configFactory = $configFactory; + $this->loggerFactory = $loggerFactory->get('lti_tool_provider'); + $this->eventDispatcher = $eventDispatcher; + $this->killSwitch = $killSwitch; + $this->request = $request; + $this->session = $session; + $this->context = $context; + $this->destination = $destination; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): LTIToolProviderController { + /* @var $ltiService LTIServiceInterface */ + $ltiService = $container->get('lti_tool_provider.lti_service'); + /* @var $configFactory ConfigFactoryInterface */ + $configFactory = $container->get('config.factory'); + /* @var $loggerFactory LoggerChannelFactoryInterface */ + $loggerFactory = $container->get('logger.factory'); + /* @var $eventDispatcher EventDispatcherInterface */ + $eventDispatcher = $container->get('event_dispatcher'); + /* @var $killSwitch ResponsePolicyInterface */ + $killSwitch = $container->get('page_cache_kill_switch'); + $request = Drupal::request(); + $session = $request->getSession(); + $context = $session->get('lti_tool_provider_context'); + $destination = Drupal::config('lti_tool_provider.settings')->get('destination'); + + return new static( + $ltiService, + $configFactory, + $loggerFactory, + $eventDispatcher, + $killSwitch, + $request, + $session, $context, - ?string $destination - ) { - $this->configFactory = $configFactory; - $this->loggerFactory = $loggerFactory->get('lti_tool_provider'); - $this->eventDispatcher = $eventDispatcher; - $this->killSwitch = $killSwitch; - $this->request = $request; - $this->session = $session; - $this->context = $context; - $this->destination = $destination; + $destination + ); + } + + /** + * LTI launch. + * + * Authenticates the user via the authentication.lti_tool_provider service, + * login that user, and then redirect the user to the appropriate page. + * + * @return RedirectResponse + * Redirect user to appropriate LTI url. + * + * @see \Drupal\lti_tool_provider\Authentication\Provider\LTIToolProvider + * This controller requires that the authentication.lti_tool_provider + * service is attached to this route in lti_tool_provider.routing.yml. + */ + public function ltiLaunch(): RedirectResponse { + try { + $destination = '/'; + + if (empty($this->context)) { + throw new Exception('LTI context missing.'); + } + + if (!empty($this->destination)) { + $destination = $this->destination; + } + + if (isset($this->context['custom_destination']) && !empty($this->context['custom_destination'])) { + $destination = $this->context['custom_destination']; + } + + $this->killSwitch->trigger(); + + $event = new LtiToolProviderLaunchRedirectEvent($this->context, $destination); + LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); + + if ($event->isCancelled()) { + throw new Exception($event->getMessage()); + } + + $destination = $event->getDestination(); + + return new RedirectResponse($destination); } + catch (Exception $e) { + $this->loggerFactory->warning($e->getMessage()); - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container): LTIToolProviderController - { - /* @var $configFactory ConfigFactoryInterface */ - $configFactory = $container->get('config.factory'); - /* @var $loggerFactory LoggerChannelFactoryInterface */ - $loggerFactory = $container->get('logger.factory'); - /* @var $eventDispatcher EventDispatcherInterface */ - $eventDispatcher = $container->get('event_dispatcher'); - /* @var $killSwitch ResponsePolicyInterface */ - $killSwitch = $container->get('page_cache_kill_switch'); - $request = Drupal::request(); - $session = $request->getSession(); - $context = $session->get('lti_tool_provider_context'); - $destination = Drupal::config('lti_tool_provider.settings')->get('destination'); - - return new static( - $configFactory, - $loggerFactory, - $eventDispatcher, - $killSwitch, - $request, - $session, - $context, - $destination - ); + return new RedirectResponse('/', 500); } - - /** - * LTI launch. - * - * Authenticates the user via the authentication.lti_tool_provider service, - * login that user, and then redirect the user to the appropriate page. - * - * @return RedirectResponse - * Redirect user to appropriate LTI url. - * - * @see \Drupal\lti_tool_provider\Authentication\Provider\LTIToolProvider - * This controller requires that the authentication.lti_tool_provider - * service is attached to this route in lti_tool_provider.routing.yml. - */ - public function ltiLaunch(): RedirectResponse - { - try { - $destination = '/'; - - if (empty($this->context)) { - throw new Exception('LTI context missing.'); - } - - if (!empty($this->destination)) { - $destination = $this->destination; - } - - if (isset($this->context['custom_destination']) && !empty($this->context['custom_destination'])) { - $destination = $this->context['custom_destination']; - } - - $this->killSwitch->trigger(); - - $event = new LtiToolProviderLaunchRedirectEvent($this->context, $destination); - LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); - - if ($event->isCancelled()) { - throw new Exception($event->getMessage()); - } - - $destination = $event->getDestination(); - - return new RedirectResponse($destination); - } - catch (Exception $e) { - $this->loggerFactory->warning($e->getMessage()); - - return new RedirectResponse('/', 500); - } + } + + /** + * LTI return. + * + * Log the user out and returns the user to the LMS. + * + * @return TrustedRedirectResponse|RedirectResponse + * Redirect user to appropriate return url. + */ + public function ltiReturn() { + try { + $destination = '/'; + + if (empty($this->context)) { + throw new Exception('LTI context missing in return request.'); + } + + if (!empty($this->destination)) { + $destination = $this->destination; + } + + if (isset($this->context['launch_presentation_return_url']) && !empty($this->context['launch_presentation_return_url'])) { + $destination = $this->context['launch_presentation_return_url']; + } + + $launch_presentation_return_url = $this->context[LTI_CLAIM_BASE_URL . 'launch_presentation']['return_url']; + if (isset($launch_presentation_return_url) && !empty($launch_presentation_return_url)) { + $destination = $launch_presentation_return_url; + } + + $this->killSwitch->trigger(); + + $event = new LtiToolProviderReturnEvent($this->context, $destination); + LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); + + if ($event->isCancelled()) { + throw new Exception($event->getMessage()); + } + + $destination = $event->getDestination(); + $this->userLogout(); + + return new TrustedRedirectResponse($destination); } + catch (Exception $e) { + $this->loggerFactory->warning($e->getMessage()); - /** - * LTI return. - * - * Logs the user out and returns the user to the LMS. - * - * @return RedirectResponse - * Redirect user to appropriate return url. - */ - public function ltiReturn() - { - try { - $destination = '/'; - - if (empty($this->context)) { - throw new Exception('LTI context missing in return request.'); - } - - if (!empty($this->destination)) { - $destination = $this->destination; - } - - if (isset($this->context['launch_presentation_return_url']) && !empty($this->context['launch_presentation_return_url'])) { - $destination = $this->context['launch_presentation_return_url']; - } - - $this->killSwitch->trigger(); - - $event = new LtiToolProviderReturnEvent($this->context, $destination); - LtiToolProviderEvent::dispatchEvent($this->eventDispatcher, $event); - - if ($event->isCancelled()) { - throw new Exception($event->getMessage()); - } - - $destination = $event->getDestination(); - $this->userLogout(); - - return new TrustedRedirectResponse($destination); - } - catch (Exception $e) { - $this->loggerFactory->warning($e->getMessage()); - - return new RedirectResponse('/', 500); + return new RedirectResponse('/', 500); + } + } + + /** + * LTI initiate login. + * + * This function works when something went wrong on initiate login. + * + * If normal way - this function is not handle. + * + * @return RedirectResponse + * Redirect user to home page. + */ + public function ltiLogin(): RedirectResponse { + $message = 'Something went wrong on initiate login. Pleas try again later!'; + $this->loggerFactory->warning($message); + \Drupal::messenger()->addError($message); + return new RedirectResponse('/'); + } + + /** + * JWKS endpoint. + * + * It allowing to supports Deep Linking and API Call. + * + * @return JsonResponse + */ + public function ltiJwks(): JsonResponse { + $public_jwks = ''; + $client_id = $_GET['client_id']; + + try { + if (isset($client_id) && !empty($client_id)) { + $consumers = \Drupal::entityTypeManager() + ->getStorage('lti_tool_provider_consumer') + ->loadByProperties([ + 'client_id' => $client_id, + ]); + $consumer = reset($consumers); + $iss = $consumer->get('platform_id')->getValue()[0]['value']; + + if (!empty($iss)) { + $public_jwks = $this->ltiService->get_public_jwks($iss, $client_id); } + } } - - /** - * Checks access for LTI routes. - * - * @return AccessResult - * The access result. - */ - public function access(): AccessResult - { - return AccessResult::allowedIf(!empty($this->context)); + catch (Exception $e) { + $this->loggerFactory->warning($e->getMessage()); } - protected function userLogout() - { - user_logout(); - } + $this->loggerFactory->warning('JWKS_Endpoint'); + return new JsonResponse($public_jwks); + } + + /** + * Checks access for LTI routes. + * + * @return AccessResult + * The access result. + */ + public function access(): AccessResult { + return AccessResult::allowedIf(!empty($this->context)); + } + + /** + * User log out method. + */ + protected function userLogout() { + user_logout(); + } + } diff --git a/src/Entity/LtiToolProviderConsumer.php b/src/Entity/LtiToolProviderConsumer.php index fcb6435..0556300 100644 --- a/src/Entity/LtiToolProviderConsumer.php +++ b/src/Entity/LtiToolProviderConsumer.php @@ -2,6 +2,7 @@ namespace Drupal\lti_tool_provider\Entity; +use Drupal\file\Entity\File; use Drupal\Core\Entity\Annotation\ContentEntityType; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Entity\ContentEntityBase; @@ -42,161 +43,429 @@ use Drupal\Core\Entity\EntityTypeInterface; * }, * ) */ -class LtiToolProviderConsumer extends ContentEntityBase implements ContentEntityInterface -{ - /** - * {@inheritdoc} - */ - public static function baseFieldDefinitions(EntityTypeInterface $entity_type): array - { - $fields = parent::baseFieldDefinitions($entity_type); - - $fields['consumer'] = BaseFieldDefinition::create('string') - ->setLabel(t('Consumer')) - ->setDescription(t('The name of the Consumer entity.')) - ->setRequired(true) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ) - ->setDisplayOptions( - 'view', - [ - 'label' => 'hidden', - 'type' => 'string', - 'weight' => 1, - ] - ) - ->setDisplayOptions( - 'form', - [ - 'type' => 'string', - 'weight' => 1, - ] - ) - ->setDisplayConfigurable('form', true) - ->setDisplayConfigurable('view', true); - - $fields['consumer_key'] = BaseFieldDefinition::create('string') - ->setLabel(t('Key')) - ->setDescription(t('The key of the Consumer entity.')) - ->setRequired(true) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ) - ->setDisplayOptions( - 'view', - [ - 'label' => 'inline', - 'type' => 'string', - 'weight' => 2, - ] - ) - ->setDisplayOptions( - 'form', - [ - 'type' => 'string', - 'weight' => 2, - ] - ) - ->setDisplayConfigurable('form', true) - ->setDisplayConfigurable('view', true); - - $fields['consumer_secret'] = BaseFieldDefinition::create('string') - ->setLabel(t('Secret')) - ->setDescription(t('The secret of the Consumer entity.')) - ->setRequired(true) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ) - ->setDisplayOptions( - 'view', - [ - 'label' => 'inline', - 'type' => 'string', - 'weight' => 3, - ] - ) - ->setDisplayOptions( - 'form', - [ - 'type' => 'string', - 'weight' => 3, - ] - ) - ->setDisplayConfigurable('form', true) - ->setDisplayConfigurable('view', true); - - $fields['name'] = BaseFieldDefinition::create('string') - ->setLabel(t('Name field')) - ->setDescription(t('The LTI field to get the users unique name from. Default is "lis_person_contact_email_primary"')) - ->setRequired(true) - ->setDefaultValue('lis_person_contact_email_primary') - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ) - ->setDisplayOptions( - 'view', - [ - 'label' => 'inline', - 'type' => 'string', - 'weight' => 4, - ] - ) - ->setDisplayOptions( - 'form', - [ - 'type' => 'string', - 'weight' => 4, - ] - ) - ->setDisplayConfigurable('form', true) - ->setDisplayConfigurable('view', true); - - $fields['mail'] = BaseFieldDefinition::create('string') - ->setLabel(t('Mail field')) - ->setDescription(t('The LTI field to get the users email from. Default is "lis_person_contact_email_primary"')) - ->setRequired(true) - ->setDefaultValue('lis_person_contact_email_primary') - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ) - ->setDisplayOptions( - 'view', - [ - 'label' => 'inline', - 'type' => 'string', - 'weight' => 5, - ] - ) - ->setDisplayOptions( - 'form', - [ - 'type' => 'string', - 'weight' => 5, - ] - ) - ->setDisplayConfigurable('form', true) - ->setDisplayConfigurable('view', true); - - $fields['created'] = BaseFieldDefinition::create('created') - ->setLabel(t('Date created')) - ->setDescription(t('Date the consumer was created')); - - return $fields; +class LtiToolProviderConsumer extends ContentEntityBase implements ContentEntityInterface { + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type): array { + $fields = parent::baseFieldDefinitions($entity_type); + + $fields['consumer'] = BaseFieldDefinition::create('string') + ->setLabel(t('Consumer')) + ->setDescription(t('The name of the Consumer entity.')) + ->setRequired(TRUE) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'hidden', + 'type' => 'string', + 'weight' => 1, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 1, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['lti_version'] = BaseFieldDefinition::create('list_string') + ->setLabel(t('LTI version')) + ->setDescription(t('LTI version of the Consumer entity.')) + ->setRequired(TRUE) + ->setSettings( + [ + 'allowed_values' => [ + 'v1p0' => 'LTI 1.0/1.1', + 'v1p3' => 'LTI 1.3', + ], + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 2, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'options_select', + 'weight' => 2, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['consumer_key'] = BaseFieldDefinition::create('string') + ->setLabel(t('Key')) + ->setDescription(t('The key of the Consumer entity.')) + ->setRequired(FALSE) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 3, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 3, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['consumer_secret'] = BaseFieldDefinition::create('string') + ->setLabel(t('Secret')) + ->setDescription(t('The secret of the Consumer entity.')) + ->setRequired(FALSE) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 4, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 4, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['platform_id'] = BaseFieldDefinition::create('string') + ->setLabel(t('Issuer (Platform Id)')) + ->setDescription(t('The issuer of Consumer entity.')) + ->setRequired(FALSE) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 5, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 5, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['client_id'] = BaseFieldDefinition::create('string') + ->setLabel(t('Client Id')) + ->setDescription(t('The Client Id of the Consumer entity.')) + ->setRequired(FALSE) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 6, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 6, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['deployment_id'] = BaseFieldDefinition::create('string') + ->setLabel(t('Deployment Id')) + ->setDescription(t('The Deployment Id of the Consumer entity.')) + ->setRequired(FALSE) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 7, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 7, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['key_set_url'] = BaseFieldDefinition::create('string') + ->setLabel(t('Public keyset URL')) + ->setDescription(t('Public keyset URL of the Consumer entity.')) + ->setRequired(FALSE) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 8, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 8, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['consumer_kid'] = BaseFieldDefinition::create('string') + ->setLabel(t('KID')) + ->setDescription(t('The KID of the Consumer entity.')) + ->setRequired(FALSE) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 9, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 9, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['auth_token_url'] = BaseFieldDefinition::create('string') + ->setLabel(t('Access token URL')) + ->setDescription(t('Access token URL of the Consumer entity.')) + ->setRequired(FALSE) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 10, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 10, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['auth_login_url'] = BaseFieldDefinition::create('string') + ->setLabel(t('Authentication request URL')) + ->setDescription(t('Authentication request URL of the Consumer entity.')) + ->setRequired(FALSE) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 11, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 11, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['private_key'] = BaseFieldDefinition::create('file') + ->setLabel(t('Private key')) + ->setDescription(t('Private key of the Consumer entity.')) + ->setRequired(TRUE) + ->setSettings([ + 'uri_scheme' => 'private', + 'file_directory' => 'private_keys', + 'file_extensions' => 'pub', + ]) + ->setDisplayOptions('view', [ + 'label' => 'inline', + 'type' => 'file', + 'weight' => 12, + ]) + ->setDisplayOptions('form', [ + 'type' => 'file', + 'weight' => 12, + ]) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['name'] = BaseFieldDefinition::create('string') + ->setLabel(t('Name field')) + ->setDescription(t('The LTI field to get the users unique name from. Default is "lis_person_name_full" for LTI 1.0/1.1. Default is "name" for LTI 1.3.')) + ->setRequired(TRUE) + ->setDefaultValue('name') + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 13, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 13, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['mail'] = BaseFieldDefinition::create('string') + ->setLabel(t('Mail field')) + ->setDescription(t('The LTI field to get the users email from. Default is "lis_person_contact_email_primary" for LTI 1.0/1.1. Default is "email" for LTI 1.3.')) + ->setRequired(TRUE) + ->setDefaultValue('email') + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ) + ->setDisplayOptions( + 'view', + [ + 'label' => 'inline', + 'type' => 'string', + 'weight' => 14, + ] + ) + ->setDisplayOptions( + 'form', + [ + 'type' => 'string', + 'weight' => 14, + ] + ) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['created'] = BaseFieldDefinition::create('created') + ->setLabel(t('Date created')) + ->setDescription(t('Date the consumer was created')); + + return $fields; + } + + /** + * Delete the LTIToolProviderConsumer entity and related private key file. + * + * @throws \Drupal\Core\Entity\EntityStorageException + */ + public function delete() { + parent::delete(); + + // Delete file if there aren't usages. + $file_usage = \Drupal::service('file.usage'); + $fid = $this->get('private_key')->getValue()[0]['target_id']; + if (isset($fid)) { + $file = File::load($fid); + $usage = $file_usage->listUsage($file); + + if (count($usage) == 0) { + $file->delete(); + } } + } + } diff --git a/src/Entity/Nonce.php b/src/Entity/Nonce.php index cb6987e..f5c1629 100644 --- a/src/Entity/Nonce.php +++ b/src/Entity/Nonce.php @@ -25,36 +25,45 @@ use Drupal\Core\Entity\EntityTypeInterface; * }, * ) */ -class Nonce extends ContentEntityBase implements ContentEntityInterface -{ - /** - * {@inheritdoc} - */ - public static function baseFieldDefinitions(EntityTypeInterface $entity_type): array - { - $fields = parent::baseFieldDefinitions($entity_type); - - $fields['nonce'] = BaseFieldDefinition::create('string') - ->setLabel(t('Nonce')) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ); - - $fields['consumer_key'] = BaseFieldDefinition::create('string') - ->setLabel(t('Consumer Key')) - ->setSettings( - [ - 'max_length' => 512, - 'text_processing' => 0, - ] - ); - - $fields['timestamp'] = BaseFieldDefinition::create('created') - ->setLabel(t('Timestamp')); - - return $fields; - } +class Nonce extends ContentEntityBase implements ContentEntityInterface { + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type): array { + $fields = parent::baseFieldDefinitions($entity_type); + + $fields['nonce'] = BaseFieldDefinition::create('string') + ->setLabel(t('Nonce')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); + + $fields['consumer_key'] = BaseFieldDefinition::create('string') + ->setLabel(t('Consumer Key')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); + + $fields['client_id'] = BaseFieldDefinition::create('string') + ->setLabel(t('Consumer Client Id')) + ->setSettings( + [ + 'max_length' => 512, + 'text_processing' => 0, + ] + ); + + $fields['timestamp'] = BaseFieldDefinition::create('created') + ->setLabel(t('Timestamp')); + + return $fields; + } + } diff --git a/src/Event/LtiToolProviderAuthenticatedEvent.php b/src/Event/LtiToolProviderAuthenticatedEvent.php index 8513a22..1814079 100644 --- a/src/Event/LtiToolProviderAuthenticatedEvent.php +++ b/src/Event/LtiToolProviderAuthenticatedEvent.php @@ -5,60 +5,61 @@ namespace Drupal\lti_tool_provider\Event; use Drupal\lti_tool_provider\LtiToolProviderEvent; use Drupal\user\UserInterface; -class LtiToolProviderAuthenticatedEvent extends LtiToolProviderEvent -{ - const EVENT_NAME = 'LTI_TOOL_PROVIDER_AUTHENTICATED_EVENT'; +/** + * Implementation LtiToolProviderAuthenticatedEvent class. + * + * @package Drupal\lti_tool_provider\Event + */ +class LtiToolProviderAuthenticatedEvent extends LtiToolProviderEvent { + const EVENT_NAME = 'LTI_TOOL_PROVIDER_AUTHENTICATED_EVENT'; - /** - * @var array - */ - private $context; + /** + * @var array + */ + private $context; - /** - * @var UserInterface - */ - private $user; + /** + * @var UserInterface + */ + private $user; - /** - * LtiToolProviderAuthenticatedEvent constructor. - * @param array $context - * @param UserInterface $user - */ - public function __construct(array $context, UserInterface $user) - { - $this->setContext($context); - $this->setUser($user); - } + /** + * LtiToolProviderAuthenticatedEvent constructor. + * + * @param array $context + * @param UserInterface $user + */ + public function __construct(array $context, UserInterface $user) { + $this->setContext($context); + $this->setUser($user); + } - /** - * @return array - */ - public function getContext(): array - { - return $this->context; - } + /** + * @return array + */ + public function getContext(): array { + return $this->context; + } - /** - * @param array $context - */ - public function setContext(array $context) - { - $this->context = $context; - } + /** + * @param array $context + */ + public function setContext(array $context) { + $this->context = $context; + } - /** - * @return UserInterface - */ - public function getUser(): UserInterface - { - return $this->user; - } + /** + * @return UserInterface + */ + public function getUser(): UserInterface { + return $this->user; + } + + /** + * @param UserInterface $user + */ + public function setUser(UserInterface $user): void { + $this->user = $user; + } - /** - * @param UserInterface $user - */ - public function setUser(UserInterface $user): void - { - $this->user = $user; - } } diff --git a/src/Event/LtiToolProviderLaunchEvent.php b/src/Event/LtiToolProviderLaunchEvent.php index 62db25b..ddc7ca5 100644 --- a/src/Event/LtiToolProviderLaunchEvent.php +++ b/src/Event/LtiToolProviderLaunchEvent.php @@ -4,37 +4,38 @@ namespace Drupal\lti_tool_provider\Event; use Drupal\lti_tool_provider\LtiToolProviderEvent; -class LtiToolProviderLaunchEvent extends LtiToolProviderEvent -{ - const EVENT_NAME = 'LTI_TOOL_PROVIDER_LAUNCH_EVENT'; - - /** - * @var array - */ - private $context; - - /** - * LtiToolProviderLaunchEvent constructor. - * @param array $context - */ - public function __construct(array $context) - { - $this->setContext($context); - } - - /** - * @return array - */ - public function getContext(): array - { - return $this->context; - } - - /** - * @param array $context - */ - public function setContext(array $context) - { - $this->context = $context; - } +/** + * Implementation LtiToolProviderLaunchEvent class. + */ +class LtiToolProviderLaunchEvent extends LtiToolProviderEvent { + const EVENT_NAME = 'LTI_TOOL_PROVIDER_LAUNCH_EVENT'; + + /** + * @var array + */ + private $context; + + /** + * LtiToolProviderLaunchEvent constructor. + * + * @param array $context + */ + public function __construct(array $context) { + $this->setContext($context); + } + + /** + * @return array + */ + public function getContext(): array { + return $this->context; + } + + /** + * @param array $context + */ + public function setContext(array $context) { + $this->context = $context; + } + } diff --git a/src/Event/LtiToolProviderLaunchRedirectEvent.php b/src/Event/LtiToolProviderLaunchRedirectEvent.php index 7479931..c2aab86 100644 --- a/src/Event/LtiToolProviderLaunchRedirectEvent.php +++ b/src/Event/LtiToolProviderLaunchRedirectEvent.php @@ -4,60 +4,60 @@ namespace Drupal\lti_tool_provider\Event; use Drupal\lti_tool_provider\LtiToolProviderEvent; -class LtiToolProviderLaunchRedirectEvent extends LtiToolProviderEvent -{ - const EVENT_NAME = 'LTI_TOOL_PROVIDER_LAUNCH_REDIRECT_EVENT'; - - /** - * @var array - */ - private $context; - - /** - * @var string - */ - private $destination; - - /** - * LtiToolProviderLaunchRedirectEvent constructor. - * @param array $context - * @param string $destination - */ - public function __construct(array $context, string $destination) - { - $this->setContext($context); - $this->setDestination($destination); - } - - /** - * @return array - */ - public function getContext(): array - { - return $this->context; - } - - /** - * @param array $context - */ - public function setContext(array $context) - { - $this->context = $context; - } - - /** - * @return string - */ - public function getDestination(): string - { - return $this->destination; - } - - /** - * @param string $destination - */ - public function setDestination(string $destination): void - { - $this->destination = $destination; - } +/** + * Impementation LtiToolProviderLaunchRedirectEvent class. + */ +class LtiToolProviderLaunchRedirectEvent extends LtiToolProviderEvent { + const EVENT_NAME = 'LTI_TOOL_PROVIDER_LAUNCH_REDIRECT_EVENT'; + + /** + * @var array + */ + private $context; + + /** + * @var string + */ + private $destination; + + /** + * LtiToolProviderLaunchRedirectEvent constructor. + * + * @param array $context + * + * @param string $destination + */ + public function __construct(array $context, string $destination) { + $this->setContext($context); + $this->setDestination($destination); + } + + /** + * @return array + */ + public function getContext(): array { + return $this->context; + } + + /** + * @param array $context + */ + public function setContext(array $context) { + $this->context = $context; + } + + /** + * @return string + */ + public function getDestination(): string { + return $this->destination; + } + + /** + * @param string $destination + */ + public function setDestination(string $destination): void { + $this->destination = $destination; + } + } diff --git a/src/Event/LtiToolProviderProvisionUserEvent.php b/src/Event/LtiToolProviderProvisionUserEvent.php index 2426649..298ea52 100644 --- a/src/Event/LtiToolProviderProvisionUserEvent.php +++ b/src/Event/LtiToolProviderProvisionUserEvent.php @@ -5,60 +5,59 @@ namespace Drupal\lti_tool_provider\Event; use Drupal\lti_tool_provider\LtiToolProviderEvent; use Drupal\user\UserInterface; -class LtiToolProviderProvisionUserEvent extends LtiToolProviderEvent -{ - const EVENT_NAME = 'LTI_TOOL_PROVIDER_PROVISION_USER_EVENT'; +/** + * Implementation LtiToolProviderProvisionUserEvent class. + */ +class LtiToolProviderProvisionUserEvent extends LtiToolProviderEvent { + const EVENT_NAME = 'LTI_TOOL_PROVIDER_PROVISION_USER_EVENT'; + + /** + * @var array + */ + private $context; + + /** + * @var \Drupal\user\UserInterface + */ + private $user; + + /** + * LtiToolProviderProvisionUserEvent constructor. + * + * @param array $context + * @param \Drupal\user\UserInterface $user + */ + public function __construct(array $context, UserInterface $user) { + $this->setContext($context); + $this->setUser($user); + } + + /** + * @return array + */ + public function getContext(): array { + return $this->context; + } + + /** + * @param array $context + */ + public function setContext(array $context) { + $this->context = $context; + } + + /** + * @return \Drupal\user\UserInterface + */ + public function getUser(): UserInterface { + return $this->user; + } + + /** + * @param \Drupal\user\UserInterface $user + */ + public function setUser(UserInterface $user): void { + $this->user = $user; + } - /** - * @var array - */ - private $context; - - /** - * @var UserInterface - */ - private $user; - - /** - * LtiToolProviderProvisionUserEvent constructor. - * @param array $context - * @param UserInterface $user - */ - public function __construct(array $context, UserInterface $user) - { - $this->setContext($context); - $this->setUser($user); - } - - /** - * @return array - */ - public function getContext(): array - { - return $this->context; - } - - /** - * @param array $context - */ - public function setContext(array $context) - { - $this->context = $context; - } - - /** - * @return UserInterface - */ - public function getUser(): UserInterface - { - return $this->user; - } - - /** - * @param UserInterface $user - */ - public function setUser(UserInterface $user): void - { - $this->user = $user; - } } diff --git a/src/Event/LtiToolProviderReturnEvent.php b/src/Event/LtiToolProviderReturnEvent.php index c94935f..b9c1110 100644 --- a/src/Event/LtiToolProviderReturnEvent.php +++ b/src/Event/LtiToolProviderReturnEvent.php @@ -4,60 +4,59 @@ namespace Drupal\lti_tool_provider\Event; use Drupal\lti_tool_provider\LtiToolProviderEvent; -class LtiToolProviderReturnEvent extends LtiToolProviderEvent -{ - const EVENT_NAME = 'LTI_TOOL_PROVIDER_RETURN_EVENT'; - - /** - * @var array - */ - private $context; - - /** - * @var string - */ - private $destination; - - /** - * LtiToolProviderReturnEvent constructor. - * @param array $context - * @param string $destination - */ - public function __construct(array $context, string $destination) - { - $this->setContext($context); - $this->setDestination($destination); - } - - /** - * @return array - */ - public function getContext(): array - { - return $this->context; - } - - /** - * @param array $context - */ - public function setContext(array $context) - { - $this->context = $context; - } - - /** - * @return string - */ - public function getDestination(): string - { - return $this->destination; - } - - /** - * @param string $destination - */ - public function setDestination(string $destination): void - { - $this->destination = $destination; - } +/** + * Impementation LtiToolProviderReturnEvent class. + */ +class LtiToolProviderReturnEvent extends LtiToolProviderEvent { + const EVENT_NAME = 'LTI_TOOL_PROVIDER_RETURN_EVENT'; + + /** + * @var array + */ + private $context; + + /** + * @var string + */ + private $destination; + + /** + * LtiToolProviderReturnEvent constructor. + * + * @param array $context + * @param string $destination + */ + public function __construct(array $context, string $destination) { + $this->setContext($context); + $this->setDestination($destination); + } + + /** + * @return array + */ + public function getContext(): array { + return $this->context; + } + + /** + * @param array $context + */ + public function setContext(array $context) { + $this->context = $context; + } + + /** + * @return string + */ + public function getDestination(): string { + return $this->destination; + } + + /** + * @param string $destination + */ + public function setDestination(string $destination): void { + $this->destination = $destination; + } + } diff --git a/src/EventSubscriber/RemoveXFrameOptionsSubscriber.php b/src/EventSubscriber/RemoveXFrameOptionsSubscriber.php index 954623a..dfa9afe 100644 --- a/src/EventSubscriber/RemoveXFrameOptionsSubscriber.php +++ b/src/EventSubscriber/RemoveXFrameOptionsSubscriber.php @@ -4,36 +4,38 @@ namespace Drupal\lti_tool_provider\EventSubscriber; use Drupal; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; -class RemoveXFrameOptionsSubscriber implements EventSubscriberInterface -{ - /** - * @param ResponseEvent $event - * - * @todo Only add ResponseEvent typing to $event once D8 is no longer supported. - */ - public function RemoveXFrameOptions($event) - { - if (Drupal::config('lti_tool_provider.settings')->get('iframe')) { - $session = $event->getRequest()->getSession(); - $context = $session->get('lti_tool_provider_context'); - - if (!empty($context) && Drupal::currentUser()->isAuthenticated()) { - $response = $event->getResponse(); - $response->headers->remove('X-Frame-Options'); - } - } - } +/** + * Implementation RemoveXFrameOptionsSubscriber class. + */ +class RemoveXFrameOptionsSubscriber implements EventSubscriberInterface { - /** - * @return array|mixed - */ - public static function getSubscribedEvents(): array - { - $events[KernelEvents::RESPONSE][] = ['RemoveXFrameOptions', -10]; + /** + * @param FilterResponseEvent $event + * + * @todo Only add ResponseEvent typing to $event once D8 is no longer supported. + */ + public function RemoveXFrameOptions(FilterResponseEvent $event) { + if (Drupal::config('lti_tool_provider.settings')->get('iframe')) { + $session = $event->getRequest()->getSession(); + $context = $session->get('lti_tool_provider_context'); - return $events; + if (!empty($context) && Drupal::currentUser()->isAuthenticated()) { + $response = $event->getResponse(); + $response->headers->remove('X-Frame-Options'); + } } + } + + /** + * @return array|mixed + */ + public static function getSubscribedEvents(): array { + $events[KernelEvents::RESPONSE][] = ['RemoveXFrameOptions', -10]; + + return $events; + } + } diff --git a/src/Form/LtiToolProviderConsumerDeleteForm.php b/src/Form/LtiToolProviderConsumerDeleteForm.php index a07f0ee..0a4daca 100644 --- a/src/Form/LtiToolProviderConsumerDeleteForm.php +++ b/src/Form/LtiToolProviderConsumerDeleteForm.php @@ -2,8 +2,8 @@ namespace Drupal\lti_tool_provider\Form; -use Drupal\Core\Entity\EntityStorageException; use Drupal; +use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Entity\ContentEntityConfirmFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -12,62 +12,59 @@ use Drupal\Core\Url; /** * Form for deleting a lti_tool_provider_consumer entity. * - * @see Drupal\lti_tool_provider\Entity\LtiToolProviderConsumer + * @package Drupal\lti_tool_provider\Form */ -class LtiToolProviderConsumerDeleteForm extends ContentEntityConfirmFormBase -{ - /** - * {@inheritdoc} - */ - public function getQuestion(): TranslatableMarkup - { - return $this->t('Are you sure you want to delete entity %name?', ['%name' => $this->entity->label()]); - } +class LtiToolProviderConsumerDeleteForm extends ContentEntityConfirmFormBase { - /** - * {@inheritdoc} - */ - public function getCancelUrl(): Url - { - return new Url('entity.lti_tool_provider_consumer.collection'); - } + /** + * {@inheritdoc} + */ + public function getQuestion(): TranslatableMarkup { + return $this->t('Are you sure you want to delete entity %name?', ['%name' => $this->entity->label()]); + } - /** - * {@inheritdoc} - */ - public function getConfirmText(): TranslatableMarkup - { - return $this->t('Delete'); - } + /** + * {@inheritdoc} + */ + public function getCancelUrl(): Url { + return new Url('entity.lti_tool_provider_consumer.collection'); + } - /** - * {@inheritdoc} - * - * Delete the entity and log the event. log() replaces the watchdog. - */ - public function submitForm(array &$form, FormStateInterface $form_state) - { - $entity = $this->getEntity(); - try { - $entity->delete(); - Drupal::logger('lti_tool_provider')->notice( - '@type: deleted %title.', - [ - '@type' => $this->entity->bundle(), - '%title' => $this->entity->label(), - ] - ); - } - catch (EntityStorageException $e) { - Drupal::logger('lti_tool_provider')->error( - '@type: error deleting %title.', - [ - '@type' => $this->entity->bundle(), - '%title' => $this->entity->label(), - ] - ); - } + /** + * {@inheritdoc} + */ + public function getConfirmText(): TranslatableMarkup { + return $this->t('Delete'); + } - $form_state->setRedirect('entity.lti_tool_provider_consumer.collection'); + /** + * {@inheritdoc} + * + * Delete the entity and log the event. log() replaces the watchdog. + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $entity = $this->getEntity(); + try { + $entity->delete(); + Drupal::logger('lti_tool_provider')->notice( + '@type: deleted %title.', + [ + '@type' => $this->entity->bundle(), + '%title' => $this->entity->label(), + ] + ); + } + catch (EntityStorageException $e) { + Drupal::logger('lti_tool_provider')->error( + '@type: error deleting %title.', + [ + '@type' => $this->entity->bundle(), + '%title' => $this->entity->label(), + ] + ); } + + $form_state->setRedirect('entity.lti_tool_provider_consumer.collection'); + } + } diff --git a/src/Form/LtiToolProviderConsumerForm.php b/src/Form/LtiToolProviderConsumerForm.php index 94e7fa9..2c6d0fd 100644 --- a/src/Form/LtiToolProviderConsumerForm.php +++ b/src/Form/LtiToolProviderConsumerForm.php @@ -12,40 +12,113 @@ use Drupal\Core\Form\FormStateInterface; * * @see \Drupal\lti_tool_provider\Entity\LtiToolProviderConsumer */ -class LtiToolProviderConsumerForm extends ContentEntityForm -{ - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state): array - { - $form = parent::buildForm($form, $form_state); - $entity = $this->entity; - - $form['langcode'] = [ - '#title' => $this->t('Language'), - '#type' => 'language_select', - '#default_value' => $entity->getUntranslated()->language()->getId(), - '#languages' => Language::STATE_ALL, - ]; - - return $form; +class LtiToolProviderConsumerForm extends ContentEntityForm { + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + $form = parent::buildForm($form, $form_state); + $entity = $this->entity; + $v1p0_lti_fields = [ + 'consumer_key', + 'consumer_secret', + ]; + $v1p3_lti_fields = [ + 'platform_id', + 'client_id', + 'deployment_id', + 'key_set_url', + 'consumer_kid', + 'auth_token_url', + 'auth_login_url', + ]; + + foreach ($v1p0_lti_fields as $lti_field) { + $form[$lti_field]['widget'][0]['value']['#states'] = [ + 'visible' => [ + ':input[name="lti_version"]' => [ + 'value' => 'v1p0', + ], + ], + 'required' => [ + ':input[name="lti_version"]' => [ + 'value' => 'v1p0', + ], + ], + ]; } - /** - * {@inheritdoc} - */ - public function save(array $form, FormStateInterface $form_state): int - { - $status = parent::save($form, $form_state); - - try { - $form_state->setRedirectUrl($this->entity->toUrl('collection')); - } - catch (EntityMalformedException $e) { - $form_state->setRedirect('entity.lti_tool_provider_consumer.collection'); - } - - return $status; + foreach ($v1p3_lti_fields as $lti_field) { + $form[$lti_field]['widget'][0]['value']['#states'] = [ + 'visible' => [ + ':input[name="lti_version"]' => [ + 'value' => 'v1p3', + ], + ], + 'required' => [ + ':input[name="lti_version"]' => [ + 'value' => 'v1p3', + ], + ], + ]; } + + $form['private_key']['#states'] = [ + 'visible' => [ + ':input[name="lti_version"]' => [ + 'value' => 'v1p3', + ], + ], + ]; + + $form['langcode'] = [ + '#title' => $this->t('Language'), + '#type' => 'language_select', + '#default_value' => $entity->getUntranslated()->language()->getId(), + '#languages' => Language::STATE_ALL, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); + + if ($form_state->getValue('lti_version')[0]['value'] === 'v1p0') { + // Temporarily store all form errors. + $form_errors = $form_state->getErrors(); + + // Clear the form errors. + $form_state->clearErrors(); + + // Remove the field_mobile form error. + unset($form_errors['private_key][0']); + + // Now loop through and re-apply the remaining form error messages. + foreach ($form_errors as $name => $error_message) { + $form_state->setErrorByName($name, $error_message); + } + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state): int { + $status = parent::save($form, $form_state); + + try { + $form_state->setRedirectUrl($this->entity->toUrl('collection')); + } + catch (EntityMalformedException $e) { + $form_state->setRedirect('entity.lti_tool_provider_consumer.collection'); + } + + return $status; + } + } diff --git a/src/Form/LtiToolProviderSettingsForm.php b/src/Form/LtiToolProviderSettingsForm.php index 008cfa4..99439a9 100644 --- a/src/Form/LtiToolProviderSettingsForm.php +++ b/src/Form/LtiToolProviderSettingsForm.php @@ -5,60 +5,62 @@ namespace Drupal\lti_tool_provider\Form; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; -class LtiToolProviderSettingsForm extends ConfigFormBase -{ - /** - * {@inheritdoc} - */ - public function getFormId(): string - { - return 'lti_tool_provider_settings'; - } +/** + * Implementation LtiToolProviderSettingsForm class. + * + * @package Drupal\lti_tool_provider\Form + */ +class LtiToolProviderSettingsForm extends ConfigFormBase { - /** - * {@inheritdoc} - */ - protected function getEditableConfigNames(): array - { - return ['lti_tool_provider.settings']; - } + /** + * {@inheritdoc} + */ + public function getFormId(): string { + return 'lti_tool_provider_settings'; + } - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state): array - { - $settings = $this->config('lti_tool_provider.settings'); + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames(): array { + return ['lti_tool_provider.settings']; + } - $form['iframe'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Allow iframe embeds'), - '#default_value' => $settings->get('iframe'), - '#description' => $this->t('Allow LTI content to be displayed in an iframe. This will disable Drupal\'s built in x-frame-options header. See this change record for more details.'), - ]; + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + $settings = $this->config('lti_tool_provider.settings'); - $form['destination'] = [ - '#type' => 'textfield', - '#title' => $this->t('Destination to redirect to after launch and successful authentication.'), - '#default_value' => $settings->get('destination'), - '#description' => $this->t( - 'Enter an internal site url, including the base path, e.g. "/front". After an LTI authentication is successful, redirect the user to this url. Note that if there is a custom destination specified in the LTI request, it will override this setting.' - ), - ]; + $form['iframe'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Allow iframe embeds'), + '#default_value' => $settings->get('iframe'), + '#description' => $this->t('Allow LTI content to be displayed in an iframe. This will disable Drupal\'s built in x-frame-options header. See this change record for more details.'), + ]; - return parent::buildForm($form, $form_state); - } + $form['destination'] = [ + '#type' => 'textfield', + '#title' => $this->t('Destination to redirect to after launch and successful authentication.'), + '#default_value' => $settings->get('destination'), + '#description' => $this->t( + 'Enter an internal site url, including the base path, e.g. "/front". After an LTI authentication is successful, redirect the user to this url. Note that if there is a custom destination specified in the LTI request, it will override this setting.' + ), + ]; - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) - { - $settings = $this->config('lti_tool_provider.settings'); + return parent::buildForm($form, $form_state); + } - $settings->set('iframe', $form_state->getValue('iframe'))->save(); - $settings->set('destination', $form_state->getValue('destination'))->save(); + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $settings = $this->config('lti_tool_provider.settings'); + + $settings->set('iframe', $form_state->getValue('iframe'))->save(); + $settings->set('destination', $form_state->getValue('destination'))->save(); + + parent::submitForm($form, $form_state); + } - parent::submitForm($form, $form_state); - } } diff --git a/src/LtiToolProviderConsumerAccessController.php b/src/LtiToolProviderConsumerAccessController.php index 8a63845..1e3336f 100644 --- a/src/LtiToolProviderConsumerAccessController.php +++ b/src/LtiToolProviderConsumerAccessController.php @@ -7,21 +7,23 @@ use Drupal\Core\Entity\EntityAccessControlHandler; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Session\AccountInterface; -class LtiToolProviderConsumerAccessController extends EntityAccessControlHandler -{ - /** - * {@inheritdoc} - */ - protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) - { - return AccessResult::allowedIfHasPermission($account, 'administer lti_tool_provider module'); - } +/** + * Implementation LtiToolProviderConsumerAccessController class. + */ +class LtiToolProviderConsumerAccessController extends EntityAccessControlHandler { + + /** + * {@inheritdoc} + */ + protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { + return AccessResult::allowedIfHasPermission($account, 'administer lti_tool_provider module'); + } + + /** + * {@inheritdoc} + */ + protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { + return AccessResult::allowedIfHasPermission($account, 'administer lti_tool_provider module'); + } - /** - * {@inheritdoc} - */ - protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = null) - { - return AccessResult::allowedIfHasPermission($account, 'administer lti_tool_provider module'); - } } diff --git a/src/LtiToolProviderConsumerListBuilder.php b/src/LtiToolProviderConsumerListBuilder.php index 5a1d337..41c75aa 100644 --- a/src/LtiToolProviderConsumerListBuilder.php +++ b/src/LtiToolProviderConsumerListBuilder.php @@ -3,90 +3,111 @@ namespace Drupal\lti_tool_provider; use Drupal\Core\Link; -use Drupal; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityListBuilder; use Drupal\Core\Url; use Drupal\lti_tool_provider\Entity\LtiToolProviderConsumer; -class LtiToolProviderConsumerListBuilder extends EntityListBuilder -{ - /** - * {@inheritdoc} - */ - public function buildHeader(): array - { - $header = [ - 'id' => [ - 'data' => $this->t('ID'), - 'field' => 'id', - 'specifier' => 'id', - ], - 'consumer' => [ - 'data' => $this->t('Label'), - 'field' => 'consumer', - 'specifier' => 'consumer', - 'class' => [RESPONSIVE_PRIORITY_LOW], - ], - 'consumer_key' => [ - 'data' => $this->t('Consumer Key'), - 'field' => 'consumer_key', - 'specifier' => 'consumer_key', - 'class' => [RESPONSIVE_PRIORITY_LOW], - ], - 'consumer_secret' => [ - 'data' => $this->t('Consumer Secret'), - 'field' => 'consumer_secret', - 'specifier' => 'consumer_secret', - 'class' => [RESPONSIVE_PRIORITY_LOW], - ], - 'created' => [ - 'data' => $this->t('Created'), - 'field' => 'created', - 'specifier' => 'created', - 'sort' => 'desc', - 'class' => [RESPONSIVE_PRIORITY_LOW], - ], - ]; +/** + * Implementation LtiToolProviderConsumerListBuilder class. + */ +class LtiToolProviderConsumerListBuilder extends EntityListBuilder { - return $header + parent::buildHeader(); - } + /** + * {@inheritdoc} + */ + public function buildHeader(): array { + $header = [ + 'id' => [ + 'data' => $this->t('ID'), + 'field' => 'id', + 'specifier' => 'id', + ], + 'consumer' => [ + 'data' => $this->t('Label'), + 'field' => 'consumer', + 'specifier' => 'consumer', + 'class' => [RESPONSIVE_PRIORITY_LOW], + ], + 'lti_version' => [ + 'data' => $this->t('LTI version'), + 'field' => 'lti_version', + 'specifier' => 'lti_version', + 'class' => [RESPONSIVE_PRIORITY_LOW], + ], + 'consumer_key' => [ + 'data' => $this->t('Consumer Key'), + 'field' => 'consumer_key', + 'specifier' => 'consumer_key', + 'class' => [RESPONSIVE_PRIORITY_LOW], + ], + 'consumer_secret' => [ + 'data' => $this->t('Consumer Secret'), + 'field' => 'consumer_secret', + 'specifier' => 'consumer_secret', + 'class' => [RESPONSIVE_PRIORITY_LOW], + ], + 'platform_id' => [ + 'data' => $this->t('Issuer (Platform Id)'), + 'field' => 'platform_id', + 'specifier' => 'platform_id', + 'class' => [RESPONSIVE_PRIORITY_LOW], + ], + 'client_id' => [ + 'data' => $this->t('Client Id'), + 'field' => 'client_id', + 'specifier' => 'client_id', + 'class' => [RESPONSIVE_PRIORITY_LOW], + ], + 'created' => [ + 'data' => $this->t('Created'), + 'field' => 'created', + 'specifier' => 'created', + 'sort' => 'desc', + 'class' => [RESPONSIVE_PRIORITY_LOW], + ], + ]; - /** - * {@inheritdoc} - */ - public function buildRow(EntityInterface $entity): array - { - $row = []; + return $header + parent::buildHeader(); + } - if ($entity instanceof LtiToolProviderConsumer) { - $row = [ - 'id' => $entity->id(), - 'consumer' => $link = Link::fromTextAndUrl( - $entity->label(), - Url::fromRoute( - 'entity.lti_tool_provider_consumer.canonical', - ['lti_tool_provider_consumer' => $entity->id()] - ) - ), - 'consumer_key' => $entity->get('consumer_key')->value, - 'consumer_secret' => $entity->get('consumer_secret')->value, - 'created' => Drupal::service('date.formatter')->format($entity->get('created')->value, 'short'), - ]; - } + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity): array { + $row = []; - return $row + parent::buildRow($entity); + if ($entity instanceof LtiToolProviderConsumer) { + $row = [ + 'id' => $entity->id(), + 'consumer' => $link = Link::fromTextAndUrl( + $entity->label(), + Url::fromRoute( + 'entity.lti_tool_provider_consumer.canonical', + ['lti_tool_provider_consumer' => $entity->id()] + ) + ), + 'lti_version' => $entity->get('lti_version')->value, + 'consumer_key' => $entity->get('consumer_key')->value, + 'consumer_secret' => $entity->get('consumer_secret')->value, + 'platform_id' => $entity->get('platform_id')->value, + 'client_id' => $entity->get('client_id')->value, + 'created' => \Drupal::service('date.formatter')->format($entity->get('created')->value, 'short'), + ]; } - /** - * {@inheritdoc} - */ - public function render(): array - { - $build = parent::render(); + return $row + parent::buildRow($entity); + } - $build['table']['#empty'] = $this->t('No consumers found.'); + /** + * {@inheritdoc} + */ + public function render(): array { + $build = parent::render(); + + $build['table']['#empty'] = $this->t('No consumers found.'); + + return $build; + } - return $build; - } } diff --git a/src/LtiToolProviderEvent.php b/src/LtiToolProviderEvent.php index a3a2ab1..4f0114b 100644 --- a/src/LtiToolProviderEvent.php +++ b/src/LtiToolProviderEvent.php @@ -3,88 +3,89 @@ namespace Drupal\lti_tool_provider; use Drupal\Core\Url; -use Exception; use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\RedirectResponse; -class LtiToolProviderEvent extends Event -{ - const EVENT_NAME = 'LTI_TOOL_PROVIDER_EVENT'; +/** + * + */ +class LtiToolProviderEvent extends Event { + const EVENT_NAME = 'LTI_TOOL_PROVIDER_EVENT'; - /** - * @var bool - */ - private $cancelled = false; + /** + * @var bool + */ + private $cancelled = FALSE; - /** - * @var string - */ - private $message; + /** + * @var string + */ + private $message; - /** - * @return bool - */ - public function isCancelled(): bool - { - return $this->cancelled; - } + /** + * @return bool + */ + public function isCancelled(): bool { + return $this->cancelled; + } - public function cancel(string $message = 'Launch has been cancelled.'): void - { - $this->cancelled = true; - $this->message = $message; - $this->stopPropagation(); - } + /** + * + */ + public function cancel(string $message = 'Launch has been cancelled.'): void { + $this->cancelled = TRUE; + $this->message = $message; + $this->stopPropagation(); + } - /** - * @return string - */ - public function getMessage(): string - { - return $this->message; - } + /** + * @return string + */ + public function getMessage(): string { + return $this->message; + } - /** - * Dispatch an LTI Tool Provider event. - * - * @param EventDispatcherInterface $eventDispatcher - * The event dispatcher. - * @param LtiToolProviderEvent $event - * The event to dispatch. - * @throws Exception - */ - static function dispatchEvent(EventDispatcherInterface $eventDispatcher, LtiToolProviderEvent &$event) - { - $event = $eventDispatcher->dispatch($event::EVENT_NAME, $event); - if ($event instanceof LtiToolProviderEvent && $event->isCancelled()) { - throw new Exception($event->getMessage()); - } + /** + * Dispatch an LTI Tool Provider event. + * + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher + * The event dispatcher. + * @param LtiToolProviderEvent $event + * The event to dispatch. + * + * @throws \Exception. + */ + public static function dispatchEvent(EventDispatcherInterface $eventDispatcher, LtiToolProviderEvent &$event) { + $event = $eventDispatcher->dispatch($event::EVENT_NAME, $event); + if ($event instanceof LtiToolProviderEvent && $event->isCancelled()) { + throw new \Exception($event->getMessage()); } + } - /** - * Send an error back to the LMS. - * - * @param array $context - * The LTI context. - * @param string $message - * The error message to send. - */ - public function sendLtiError(array $context, string $message) - { - if (isset($context['launch_presentation_return_url']) && !empty($context['launch_presentation_return_url'])) { - $url = Url::fromUri($context['launch_presentation_return_url']) - ->setOption( - 'query', - [ - 'lti_errormsg' => $message, - ] - ) - ->setAbsolute(true) - ->toString(); + /** + * Send an error back to the LMS. + * + * @param array $context + * The LTI context. + * @param string $message + * The error message to send. + */ + public function sendLtiError(array $context, string $message) { + if (isset($context['launch_presentation_return_url']) && !empty($context['launch_presentation_return_url'])) { + $url = Url::fromUri($context['launch_presentation_return_url']) + ->setOption( + 'query', + [ + 'lti_errormsg' => $message, + ] + ) + ->setAbsolute(TRUE) + ->toString(); - $response = new RedirectResponse($url); - $response->send(); - } + $response = new RedirectResponse($url); + $response->send(); } + } + } diff --git a/src/Services/LTIDatabase.php b/src/Services/LTIDatabase.php new file mode 100644 index 0000000..a21d5dc --- /dev/null +++ b/src/Services/LTIDatabase.php @@ -0,0 +1,108 @@ +getStorage('lti_tool_provider_consumer'); + $consumer_params = [ + 'platform_id' => $iss, + ]; + + if (isset($_POST['client_id'])) { + $consumer_params['client_id'] = $_POST['client_id']; + } + + if (isset($_POST['state']) && isset($_COOKIE['lti1p3_client_id'])) { + $consumer_params['client_id'] = $_COOKIE['lti1p3_client_id']; + } + + if (isset($client_id)) { + $consumer_params['client_id'] = $client_id; + } + + $consumers = $consumer_storage->loadByProperties($consumer_params); + $consumer = reset($consumers); + + if (isset($consumer)) { + return LTI_Registration::new() + ->set_auth_login_url($consumer->get('auth_login_url')->getValue()[0]['value']) + ->set_auth_token_url($consumer->get('auth_token_url')->getValue()[0]['value']) + ->set_client_id($consumer->get('client_id')->getValue()[0]['value']) + ->set_key_set_url($consumer->get('key_set_url')->getValue()[0]['value']) + ->set_kid($consumer->get('consumer_kid')->getValue()[0]['value']) + ->set_issuer($consumer->get('platform_id')->getValue()[0]['value']) + ->set_tool_private_key($this->private_key($consumer->get('private_key')->getValue()[0]['target_id'])); + } + else { + return NULL; + } + } + + /** + * Implementation IMSGlobal\LTI\Database find_deployment method. + * + * @param $iss + * + * @param $deployment_id + * + * @return false|LTI_Deployment + */ + public function find_deployment($iss, $deployment_id) { + $consumer_storage = \Drupal::entityTypeManager()->getStorage('lti_tool_provider_consumer'); + $consumer_params = [ + 'platform_id' => $iss, + ]; + + if (isset($_POST['state']) && isset($_COOKIE['lti1p3_client_id'])) { + $consumer_params['client_id'] = $_COOKIE['lti1p3_client_id']; + } + + $consumers = $consumer_storage->loadByProperties($consumer_params); + $consumer = reset($consumers); + $consumer_deployment_id = $consumer->get('deployment_id')->getValue()[0]['value']; + + if (isset($consumer)) { + if ($consumer_deployment_id != $deployment_id) { + return FALSE; + } + return LTI_Deployment::new() + ->set_deployment_id($deployment_id); + } + else { + return FALSE; + } + } + + /** + * Get RSA private key content. + * + * @param $fid + * @return false|string + */ + private function private_key($fid) { + $file = File::load($fid); + return file_get_contents($file->getFileUri()); + } + +} diff --git a/src/Services/LTIService.php b/src/Services/LTIService.php new file mode 100644 index 0000000..e3b944b --- /dev/null +++ b/src/Services/LTIService.php @@ -0,0 +1,146 @@ +registration = $lti_database; + } + + /** + * Open Id Connect request parameters validation before Login Request. + * + * Request parameters: iss, target_link_uri, login_hint, lti_message_hint. + * + * @return bool + */ + public function pre_login_validate(Request $request): bool { + $lti_message_type = $request->request->get('lti_message_type'); + $lti_version = $request->request->get('lti_version'); + $oauth_consumer_key = $request->request->get('oauth_consumer_key'); + $resource_link_id = $request->request->get('resource_link_id'); + $iss = $request->request->get('iss'); + $target_link_uri = $request->request->get('target_link_uri'); + $login_hint = $request->request->get('login_hint'); + $lti_message_hint = $request->request->get('lti_message_hint'); + $client_id = $request->request->get('client_id'); + $lti_deployment_id = $request->request->get('lti_deployment_id'); + $id_token = $request->request->get('id_token'); + $state = $request->request->get('state'); + + $is_lti_message_type = is_string($lti_message_type) && strlen($lti_message_type) > 0; + $is_lti_version = is_string($lti_version) && strlen($lti_version) > 0; + $is_oauth_consumer_key = is_string($oauth_consumer_key) && strlen($oauth_consumer_key) > 0; + $is_resource_link_id = is_string($resource_link_id) && strlen($resource_link_id) > 0; + $is_iss = is_string($iss) && strlen($iss) > 0; + $is_target_link_uri = is_string($target_link_uri) && strlen($target_link_uri) > 0; + $is_login_hint = is_string($login_hint) && strlen($login_hint) > 0; + $is_lti_message_hint = is_string($lti_message_hint) && strlen($lti_message_hint) > 0; + $is_client_id = is_string($client_id) && strlen($client_id) > 0; + $is_lti_deployment_id = is_string($lti_deployment_id) && strlen($lti_deployment_id) > 0; + $is_id_token = is_string($id_token) && strlen($id_token) > 0; + $is_state = is_string($state) && strlen($state) > 0; + + // LTI v1.0. + if ($is_lti_message_type and $is_lti_version and $is_oauth_consumer_key and $is_resource_link_id) { + if (!$request->isMethod('POST')) { + return FALSE; + } + + if ($lti_message_type !== 'basic-lti-launch-request') { + return FALSE; + } + + if (!in_array($lti_version, ['LTI-1p0', 'LTI-1p2'])) { + return FALSE; + } + + return TRUE; + } + // LTI v.1.3. + elseif ($is_iss and $is_target_link_uri and $is_login_hint and $is_lti_message_hint and $is_client_id and $is_lti_deployment_id) { + return TRUE; + } + elseif ($is_id_token and $is_state) { + return TRUE; + } + elseif ($request->getRequestUri() == '/lti/return') { + return TRUE; + } + else { + return FALSE; + } + } + + /** + * Open Id Connect Login Request. + * + * Https://github.com/IMSGlobal/lti-1-3-php-library#user-content-handling-requests. + * + * @throws \IMSGlobal\LTI\OIDC_Exception + */ + public function login() { + $redirect_path = \Drupal::request()->getSchemeAndHttpHost() . '/lti/launch'; + + // Set client id to Cookies. + $cookie_options = [ + 'expires' => time() + 60, + ]; + setcookie('lti1p3_client_id', $_POST['client_id'], $cookie_options); + + // This is a LTI v1.3 Login request. + LTI_OIDC_Login::new($this->registration) + ->do_oidc_login_redirect($redirect_path) + ->do_redirect(); + } + + /** + * LTI Message Launches. + * + * https://github.com/IMSGlobal/lti-1-3-php-library#user-content-lti-message-launches + * + * @return \IMSGlobal\LTI\LTI_Message_Launch|mixed + * Return launch object + * + * @throws \IMSGlobal\LTI\LTI_Exception + */ + public function validate() { + return LTI_Message_Launch::new($this->registration) + ->validate(); + } + + /** + * Get JWKS object. + * + * @param $iss + * @param $client_id + * @return array[] + */ + public function get_public_jwks($iss, $client_id) { + $registration = $this->registration->find_registration_by_issuer($iss, $client_id); + $jwks = new JWKS_Endpoint([$registration->get_kid() => $registration->get_tool_private_key()]); + + return $jwks->get_public_jwks(); + } + +} diff --git a/src/Services/LTIServiceInterface.php b/src/Services/LTIServiceInterface.php new file mode 100644 index 0000000..9c22f65 --- /dev/null +++ b/src/Services/LTIServiceInterface.php @@ -0,0 +1,31 @@ +