From 3df280f2ba87522febf99fc7f79e59d1e8117a5c Mon Sep 17 00:00:00 2001 From: Colin Beeby Date: Mon, 24 Mar 2025 13:32:11 +0000 Subject: [PATCH 1/4] Corrected the vm_size in the aks default node pool --- Terraform/test/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terraform/test/main.tf b/Terraform/test/main.tf index 0b4c8692902..40507a17f27 100644 --- a/Terraform/test/main.tf +++ b/Terraform/test/main.tf @@ -36,7 +36,7 @@ resource "azurerm_kubernetes_cluster" "aks" { dns_prefix = var.ClusterName default_node_pool { name = "default" - vm_size = "Standard_B4ms" + vm_size = "Standard_D4s_v3" temporary_name_for_rotation = "tmpnodepool1" auto_scaling_enabled = true min_count = 2 From c561175c2ee4e86d7592016e08462d763f4ddacf Mon Sep 17 00:00:00 2001 From: Colin Beeby Date: Mon, 24 Mar 2025 14:01:50 +0000 Subject: [PATCH 2/4] Corrected the vm_size in the aks default node pool --- .github/workflows/learninghub-moodle_Deploy_test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/learninghub-moodle_Deploy_test.yml b/.github/workflows/learninghub-moodle_Deploy_test.yml index 0d1f327d699..785f2758336 100644 --- a/.github/workflows/learninghub-moodle_Deploy_test.yml +++ b/.github/workflows/learninghub-moodle_Deploy_test.yml @@ -278,7 +278,7 @@ jobs: - name: Disable automounting API credentials run: | - kubectl patch deployment learninghubmoodledev -p "{\"spec\": {\"template\": {\"spec\": {\"automountServiceAccountToken\": false}}}}" -n learninghubmoodle + kubectl patch deployment learninghubmoodletest -p "{\"spec\": {\"template\": {\"spec\": {\"automountServiceAccountToken\": false}}}}" -n learninghubmoodle kubectl patch serviceaccount default -p "{\"automountServiceAccountToken\": false}" -n learninghubmoodle - name: Get node resource group @@ -313,4 +313,4 @@ jobs: - name: Define trusted container registries run: | - az policy assignment create --resource-group ${{ vars.AZURE_RESOURCE_GROUP_NAME }} --name "EnforceTrustedRegistries" --policy "febd0533-8e55-448f-b837-bd0e06f16469" --params "{\"allowedContainerImagesRegex\":{\"value\":\"learninghubmoodlecrdev.azurecr.io\"}}" --location uksouth \ No newline at end of file + az policy assignment create --resource-group ${{ vars.AZURE_RESOURCE_GROUP_NAME }} --name "EnforceTrustedRegistries" --policy "febd0533-8e55-448f-b837-bd0e06f16469" --params "{\"allowedContainerImagesRegex\":{\"value\":\"learninghubmoodlecrtest.azurecr.io\"}}" --location uksouth \ No newline at end of file From 6b23287e571e0ac7a5cebaa95066c1c1fc7e4eb7 Mon Sep 17 00:00:00 2001 From: Binon Date: Mon, 31 Mar 2025 10:38:53 +0100 Subject: [PATCH 3/4] openId implementation --- auth/oidc/.github/PULL_REQUEST_TEMPLATE.txt | 7 + auth/oidc/.github/workflows/ci.yml | 126 +++ auth/oidc/.gitlab-ci.yml | 85 ++ auth/oidc/LICENSE | 674 +++++++++++++++ auth/oidc/README.md | 31 + auth/oidc/SECURITY.md | 41 + auth/oidc/auth.php | 338 ++++++++ auth/oidc/binding_username_claim.php | 109 +++ .../change_binding_username_claim_tool.php | 128 +++ .../auth_oidc_admin_setting_iconselect.php | 123 +++ .../auth_oidc_admin_setting_label.php | 83 ++ .../auth_oidc_admin_setting_loginflow.php | 97 +++ .../auth_oidc_admin_setting_redirecturi.php | 94 ++ auth/oidc/classes/adminsetting/iconselect.css | 26 + auth/oidc/classes/event/action_failed.php | 60 ++ auth/oidc/classes/event/user_authed.php | 60 ++ auth/oidc/classes/event/user_connected.php | 61 ++ auth/oidc/classes/event/user_created.php | 61 ++ auth/oidc/classes/event/user_disconnected.php | 61 ++ auth/oidc/classes/event/user_loggedin.php | 61 ++ .../classes/event/user_rename_attempt.php | 64 ++ auth/oidc/classes/form/application.php | 281 ++++++ .../classes/form/binding_username_claim.php | 138 +++ ...ange_binding_username_claim_tool_form1.php | 73 ++ ...ange_binding_username_claim_tool_form2.php | 53 ++ auth/oidc/classes/form/disconnect.php | 97 +++ auth/oidc/classes/httpclient.php | 110 +++ auth/oidc/classes/httpclientinterface.php | 41 + auth/oidc/classes/jwt.php | 155 ++++ auth/oidc/classes/loginflow/authcode.php | 818 ++++++++++++++++++ auth/oidc/classes/loginflow/base.php | 780 +++++++++++++++++ auth/oidc/classes/loginflow/rocreds.php | 193 +++++ auth/oidc/classes/observers.php | 51 ++ auth/oidc/classes/oidcclient.php | 435 ++++++++++ auth/oidc/classes/preview.php | 143 +++ auth/oidc/classes/privacy/provider.php | 242 ++++++ auth/oidc/classes/process.php | 296 +++++++ auth/oidc/classes/task/cleanup_oidc_sid.php | 49 ++ .../task/cleanup_oidc_state_and_token.php | 53 ++ auth/oidc/classes/tests/mockhttpclient.php | 83 ++ auth/oidc/classes/tests/mockoidcclient.php | 55 ++ auth/oidc/classes/upload_process_tracker.php | 149 ++++ auth/oidc/classes/utils.php | 247 ++++++ auth/oidc/cleanupoidctokens.php | 107 +++ auth/oidc/db/access.php | 49 ++ auth/oidc/db/events.php | 35 + auth/oidc/db/install.php | 35 + auth/oidc/db/install.xml | 75 ++ auth/oidc/db/tasks.php | 47 + auth/oidc/db/upgrade.php | 559 ++++++++++++ auth/oidc/example.csv | 2 + auth/oidc/index.php | 32 + auth/oidc/js/module.js | 55 ++ auth/oidc/lang/cs/auth_oidc.php | 133 +++ auth/oidc/lang/de/auth_oidc.php | 133 +++ auth/oidc/lang/en/auth_oidc.php | 499 +++++++++++ auth/oidc/lang/es/auth_oidc.php | 133 +++ auth/oidc/lang/fi/auth_oidc.php | 133 +++ auth/oidc/lang/fr/auth_oidc.php | 169 ++++ auth/oidc/lang/it/auth_oidc.php | 133 +++ auth/oidc/lang/ja/auth_oidc.php | 133 +++ auth/oidc/lang/nl/auth_oidc.php | 133 +++ auth/oidc/lang/pl/auth_oidc.php | 133 +++ auth/oidc/lang/pt_br/auth_oidc.php | 133 +++ auth/oidc/lib.php | 772 +++++++++++++++++ auth/oidc/logout.php | 50 ++ auth/oidc/manageapplication.php | 150 ++++ auth/oidc/pix/o365.png | Bin 0 -> 497 bytes auth/oidc/settings.php | 258 ++++++ auth/oidc/styles.css | 49 ++ auth/oidc/tests/jwt_test.php | 132 +++ auth/oidc/tests/oidcclient_test.php | 132 +++ auth/oidc/tests/privacy_provider_test.php | 314 +++++++ auth/oidc/ucp.php | 110 +++ auth/oidc/version.php | 32 + 75 files changed, 11792 insertions(+) create mode 100644 auth/oidc/.github/PULL_REQUEST_TEMPLATE.txt create mode 100644 auth/oidc/.github/workflows/ci.yml create mode 100644 auth/oidc/.gitlab-ci.yml create mode 100644 auth/oidc/LICENSE create mode 100644 auth/oidc/README.md create mode 100644 auth/oidc/SECURITY.md create mode 100644 auth/oidc/auth.php create mode 100644 auth/oidc/binding_username_claim.php create mode 100644 auth/oidc/change_binding_username_claim_tool.php create mode 100644 auth/oidc/classes/adminsetting/auth_oidc_admin_setting_iconselect.php create mode 100644 auth/oidc/classes/adminsetting/auth_oidc_admin_setting_label.php create mode 100644 auth/oidc/classes/adminsetting/auth_oidc_admin_setting_loginflow.php create mode 100644 auth/oidc/classes/adminsetting/auth_oidc_admin_setting_redirecturi.php create mode 100644 auth/oidc/classes/adminsetting/iconselect.css create mode 100644 auth/oidc/classes/event/action_failed.php create mode 100644 auth/oidc/classes/event/user_authed.php create mode 100644 auth/oidc/classes/event/user_connected.php create mode 100644 auth/oidc/classes/event/user_created.php create mode 100644 auth/oidc/classes/event/user_disconnected.php create mode 100644 auth/oidc/classes/event/user_loggedin.php create mode 100644 auth/oidc/classes/event/user_rename_attempt.php create mode 100644 auth/oidc/classes/form/application.php create mode 100644 auth/oidc/classes/form/binding_username_claim.php create mode 100644 auth/oidc/classes/form/change_binding_username_claim_tool_form1.php create mode 100644 auth/oidc/classes/form/change_binding_username_claim_tool_form2.php create mode 100644 auth/oidc/classes/form/disconnect.php create mode 100644 auth/oidc/classes/httpclient.php create mode 100644 auth/oidc/classes/httpclientinterface.php create mode 100644 auth/oidc/classes/jwt.php create mode 100644 auth/oidc/classes/loginflow/authcode.php create mode 100644 auth/oidc/classes/loginflow/base.php create mode 100644 auth/oidc/classes/loginflow/rocreds.php create mode 100644 auth/oidc/classes/observers.php create mode 100644 auth/oidc/classes/oidcclient.php create mode 100644 auth/oidc/classes/preview.php create mode 100644 auth/oidc/classes/privacy/provider.php create mode 100644 auth/oidc/classes/process.php create mode 100644 auth/oidc/classes/task/cleanup_oidc_sid.php create mode 100644 auth/oidc/classes/task/cleanup_oidc_state_and_token.php create mode 100644 auth/oidc/classes/tests/mockhttpclient.php create mode 100644 auth/oidc/classes/tests/mockoidcclient.php create mode 100644 auth/oidc/classes/upload_process_tracker.php create mode 100644 auth/oidc/classes/utils.php create mode 100644 auth/oidc/cleanupoidctokens.php create mode 100644 auth/oidc/db/access.php create mode 100644 auth/oidc/db/events.php create mode 100644 auth/oidc/db/install.php create mode 100644 auth/oidc/db/install.xml create mode 100644 auth/oidc/db/tasks.php create mode 100644 auth/oidc/db/upgrade.php create mode 100644 auth/oidc/example.csv create mode 100644 auth/oidc/index.php create mode 100644 auth/oidc/js/module.js create mode 100644 auth/oidc/lang/cs/auth_oidc.php create mode 100644 auth/oidc/lang/de/auth_oidc.php create mode 100644 auth/oidc/lang/en/auth_oidc.php create mode 100644 auth/oidc/lang/es/auth_oidc.php create mode 100644 auth/oidc/lang/fi/auth_oidc.php create mode 100644 auth/oidc/lang/fr/auth_oidc.php create mode 100644 auth/oidc/lang/it/auth_oidc.php create mode 100644 auth/oidc/lang/ja/auth_oidc.php create mode 100644 auth/oidc/lang/nl/auth_oidc.php create mode 100644 auth/oidc/lang/pl/auth_oidc.php create mode 100644 auth/oidc/lang/pt_br/auth_oidc.php create mode 100644 auth/oidc/lib.php create mode 100644 auth/oidc/logout.php create mode 100644 auth/oidc/manageapplication.php create mode 100644 auth/oidc/pix/o365.png create mode 100644 auth/oidc/settings.php create mode 100644 auth/oidc/styles.css create mode 100644 auth/oidc/tests/jwt_test.php create mode 100644 auth/oidc/tests/oidcclient_test.php create mode 100644 auth/oidc/tests/privacy_provider_test.php create mode 100644 auth/oidc/ucp.php create mode 100644 auth/oidc/version.php diff --git a/auth/oidc/.github/PULL_REQUEST_TEMPLATE.txt b/auth/oidc/.github/PULL_REQUEST_TEMPLATE.txt new file mode 100644 index 00000000000..98e691295ff --- /dev/null +++ b/auth/oidc/.github/PULL_REQUEST_TEMPLATE.txt @@ -0,0 +1,7 @@ +*** PLEASE DO NOT OPEN PULL REQUESTS IN THIS REPO *** + +This is a read-only repository for Moodle plugins directory release process. All developments are carried out in the main project repository at https://github.com/microsoft/o365-moodle. Please create your pull requests there. + +Thank you. + +-- \ No newline at end of file diff --git a/auth/oidc/.github/workflows/ci.yml b/auth/oidc/.github/workflows/ci.yml new file mode 100644 index 00000000000..01e29dc0cc5 --- /dev/null +++ b/auth/oidc/.github/workflows/ci.yml @@ -0,0 +1,126 @@ +name: Moodle Plugin CI for auth_oidc + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - 'MOODLE_*_STABLE' + pull_request: + +jobs: + check: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13 + env: + POSTGRES_USER: 'postgres' + POSTGRES_HOST_AUTH_METHOD: 'trust' + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 + + mariadb: + image: mariadb:10 + env: + MYSQL_USER: 'root' + MYSQL_ALLOW_EMPTY_PASSWORD: "true" + MYSQL_CHARACTER_SET_SERVER: "utf8mb4" + MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci" + MYSQL_INNODB_FILE_PER_TABLE: "1" + MYSQL_INNODB_FILE_FORMAT: "Barracuda" + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3 + + strategy: + fail-fast: false + matrix: + moodle-branch: ['MOODLE_405_STABLE'] + php: [8.1, 8.2, 8.3] + database: [pgsql, mariadb] + + steps: + - name: Check out repository code + uses: actions/checkout@v4 + with: + path: plugin + + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + ini-values: max_input_vars=5000 + coverage: none + + - name: Initialise moodle-plugin-ci + run: | + composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^4 + echo $(cd ci/bin; pwd) >> $GITHUB_PATH + echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH + sudo locale-gen en_AU.UTF-8 + echo "NVM_DIR=$HOME/.nvm" >> $GITHUB_ENV + + - name: Install moodle-plugin-ci + run: | + moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 + env: + DB: ${{ matrix.database }} + MOODLE_BRANCH: ${{ matrix.moodle-branch }} + SHELLOPTS: errexit:nounset:xtrace + + - name: PHP Lint + if: ${{ !cancelled() }} + run: moodle-plugin-ci phplint + + - name: PHP Mess Detector + continue-on-error: true + if: ${{ !cancelled() }} + run: moodle-plugin-ci phpmd + + - name: Moodle Code Checker + if: ${{ !cancelled() }} + run: moodle-plugin-ci phpcs --max-warnings 0 + + - name: Moodle PHPDoc Checker + if: ${{ !cancelled() }} + run: moodle-plugin-ci phpdoc --max-warnings 0 + + - name: Validating + if: ${{ !cancelled() }} + run: moodle-plugin-ci validate + + - name: Check upgrade savepoints + if: ${{ !cancelled() }} + run: moodle-plugin-ci savepoints + + - name: Mustache Lint + if: ${{ !cancelled() }} + run: moodle-plugin-ci mustache + + - name: Grunt + if: ${{ !cancelled() }} + run: moodle-plugin-ci grunt --max-lint-warnings 0 + + - name: PHPUnit tests + if: ${{ !cancelled() }} + run: moodle-plugin-ci phpunit + + - name: Behat features + id: behat + if: ${{ !cancelled() }} + run: moodle-plugin-ci behat --profile chrome + + - name: Cleanup after behat + if: ${{ always() }} + run: | + sudo pkill -f chrome + sudo pkill -f chromedriver + + - name: Mark cancelled jobs as failed. + if: ${{ cancelled() }} + run: exit 1 \ No newline at end of file diff --git a/auth/oidc/.gitlab-ci.yml b/auth/oidc/.gitlab-ci.yml new file mode 100644 index 00000000000..bbb20636c8b --- /dev/null +++ b/auth/oidc/.gitlab-ci.yml @@ -0,0 +1,85 @@ +services: + - name: selenium/standalone-chrome:3 + alias: behat + - name: mysql:8.0 + alias: db + command: + - '--character-set-server=utf8mb4' + - '--collation-server=utf8mb4_unicode_ci' + - '--innodb_file_per_table=On' + - '--wait-timeout=28800' + - '--skip-log-bin' + +cache: + paths: + - .cache + +variables: + DEBIAN_FRONTEND: 'noninteractive' + COMPOSER_ALLOW_SUPERUSER: 1 + COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.cache/composer" + NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.cache/npm" + CI_BUILD_DIR: '/tmp/plugin' + MOODLE_BRANCH: 'MOODLE_405_STABLE' + MOODLE_BEHAT_WWWROOT: 'http://localhost:8000' + MOODLE_BEHAT_WDHOST: 'http://behat:4444/wd/hub' + MOODLE_START_BEHAT_SERVERS: 'no' + MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' + DB: 'mysqli' + +stages: + - moodle-plugin-ci + +.setupandruncheck: &setupandruncheck + stage: moodle-plugin-ci + before_script: + - mkdir -pv "$CI_BUILD_DIR" + - cp -ru "$CI_PROJECT_DIR/"* "$CI_BUILD_DIR" + - mkdir -p /usr/share/man/man1 /usr/share/man/man3 /usr/share/man/man7 + - apt-get -qq update + - apt-get -yqq install --no-install-suggests default-jre-headless default-mysql-client + - 'curl -sS https://raw.githubusercontent.com/creationix/nvm/v0.39.3/install.sh | bash' + - . ~/.bashrc + - nvm install --default --latest-npm lts/gallium + - 'curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer' + - composer create-project -n --no-dev --no-progress --no-ansi moodlehq/moodle-plugin-ci /opt/mci ^4 + - export PATH="/opt/mci/bin:/opt/mci/vendor/bin:$PATH" + - moodle-plugin-ci install --db-host db --db-name moodle + - '{ php -S 0.0.0.0:8000 -t "$CI_PROJECT_DIR/moodle" >/dev/null 2>&1 & }' + - TXT_RED="\e[31m" + + script: + - errors=() + - moodle-plugin-ci phplint || errors+=("phplint") + - moodle-plugin-ci phpmd || errors+=("phpmd") + - moodle-plugin-ci codechecker --max-warnings 0 || errors+=("codechecker") + - moodle-plugin-ci phpdoc --max-warnings 0 || errors+=("phpdoc") + - moodle-plugin-ci validate || errors+=("validate") + - moodle-plugin-ci savepoints || errors+=("savepoints") + - moodle-plugin-ci mustache || errors+=("mustache") + - moodle-plugin-ci grunt --max-lint-warnings 0 || errors+=("grunt") + - moodle-plugin-ci phpunit || errors+=("phpunit") + - moodle-plugin-ci behat --auto-rerun 0 --profile chrome || errors+=("behat") + - |- + if [ ${#errors[@]} -ne 0 ]; then + echo -e "${TXT_RED}Check errors: ${errors[@]}"; + exit 1; + fi + +php81: + tags: + - docker + image: moodlehq/moodle-php-apache:8.1 + <<: *setupandruncheck + +php82: + tags: + - docker + image: moodlehq/moodle-php-apache:8.2 + <<: *setupandruncheck + +php83: + tags: + - docker + image: moodlehq/moodle-php-apache:8.3 + <<: *setupandruncheck diff --git a/auth/oidc/LICENSE b/auth/oidc/LICENSE new file mode 100644 index 00000000000..20d40b6bcec --- /dev/null +++ b/auth/oidc/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/auth/oidc/README.md b/auth/oidc/README.md new file mode 100644 index 00000000000..b8baf4458d4 --- /dev/null +++ b/auth/oidc/README.md @@ -0,0 +1,31 @@ +# Microsoft 365 and Microsoft Entra ID Plugins for Moodle + +## OpenID Connect Authentication Plugin. + +The OpenID Connect plugin provides single-sign-on functionality using configurable identity providers. + +This is part of the suite of Microsoft 365 plugins for Moodle. + +This repository is updated with stable releases. To follow active development, see: https://github.com/Microsoft/o365-moodle + +## Installation + +1. Unpack the plugin into /auth/oidc within your Moodle install. +2. From the Moodle Administration block, expand Site Administration and click "Notifications". +3. Follow the on-screen instuctions to install the plugin. +4. To configure the plugin, from the Moodle Administration block, go to Site Administration > Plugins > Authentication > Manage Authentication. +5. Click the icon to enable the plugin, then visit the settings page to configure the plugin. Follow the directions below each setting. + +For more documentation, visit https://docs.moodle.org/34/en/Office365 + +For more information including support and instructions on how to contribute, please see: https://github.com/Microsoft/o365-moodle/blob/master/README.md + +## Issues and Contributing +Please post issues for this plugin to: https://github.com/Microsoft/o365-moodle/issues/ +Pull requests for this plugin should be submitted against our main repository: https://github.com/Microsoft/o365-moodle + +## Copyright + +© Microsoft, Inc. Code for this plugin is licensed under the GPLv3 license. + +Any Microsoft trademarks and logos included in these plugins are property of Microsoft and should not be reused, redistributed, modified, repurposed, or otherwise altered or used outside of this plugin. diff --git a/auth/oidc/SECURITY.md b/auth/oidc/SECURITY.md new file mode 100644 index 00000000000..869fdfe2b24 --- /dev/null +++ b/auth/oidc/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + diff --git a/auth/oidc/auth.php b/auth/oidc/auth.php new file mode 100644 index 00000000000..cd65c379a14 --- /dev/null +++ b/auth/oidc/auth.php @@ -0,0 +1,338 @@ +. + +/** + * OpenID Connect authentication plugin declaration. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir.'/authlib.php'); +require_once($CFG->dirroot.'/login/lib.php'); + +/** + * OpenID Connect Authentication Plugin. + */ +class auth_plugin_oidc extends \auth_plugin_base { + /** @var string Authentication plugin type - the same as db field. */ + public $authtype = 'oidc'; + + /** @var object Plugin config. */ + public $config; + + /** @var object extending \auth_oidc\loginflow\base */ + public $loginflow; + + /** + * Constructor. + * + * @param null $forceloginflow + * @throws moodle_exception + */ + public function __construct($forceloginflow = null) { + global $SESSION; + $loginflow = 'authcode'; + + if (isset($SESSION->stateadditionaldata) && !empty($SESSION->stateadditionaldata) && + isset($SESSION->stateadditoinaldata['forceflow'])) { + $loginflow = $SESSION->stateadditoinaldata['forceflow']; + } else { + if (!empty($forceloginflow) && is_string($forceloginflow)) { + $loginflow = $forceloginflow; + } else { + $configuredloginflow = get_config('auth_oidc', 'loginflow'); + if (!empty($configuredloginflow)) { + $loginflow = $configuredloginflow; + } + } + } + $loginflowclass = '\auth_oidc\loginflow\\'.$loginflow; + if (class_exists($loginflowclass)) { + $this->loginflow = new $loginflowclass($this->config); + } else { + throw new moodle_exception('errorbadloginflow', 'auth_oidc'); + } + $this->config = $this->loginflow->config; + } + + /** + * Returns true if plugin can be manually set. + * + * @return bool + */ + public function can_be_manually_set() { + return true; + } + + /** + * Returns a list of potential IdPs that this authentication plugin supports. Used to provide links on the login page. + * + * @param string $wantsurl The relative url fragment the user wants to get to. + * @return array Array of idps. + */ + public function loginpage_idp_list($wantsurl) { + return $this->loginflow->loginpage_idp_list($wantsurl); + } + + /** + * Set an HTTP client to use. + * + * @param \auth_oidc\httpclientinterface $httpclient + * @return mixed + */ + public function set_httpclient(\auth_oidc\httpclientinterface $httpclient) { + return $this->loginflow->set_httpclient($httpclient); + } + + /** + * Hook for overriding behaviour of login page. + * This method is called from login/index.php page for all enabled auth plugins. + */ + public function loginpage_hook() { + global $frm; // Can be used to override submitted login form. + global $user; // Can be used to replace authenticate_user_login(). + if ($this->should_login_redirect()) { + $this->loginflow->handleredirect(); + } + return $this->loginflow->loginpage_hook($frm, $user); + } + + /** + * Determines if we will redirect to the redirecturi. + * + * @return bool If this returns true then redirect + */ + public function should_login_redirect() { + global $CFG, $SESSION; + + $oidc = optional_param('oidc', null, PARAM_BOOL); + // Also support noredirect param - used by other auth plugins. + $noredirect = optional_param('noredirect', 0, PARAM_BOOL); + if (!empty($noredirect)) { + $oidc = 0; + } + if (!isset($this->config->forceredirect) || !$this->config->forceredirect) { + return false; // Never redirect if we haven't enabled the forceredirect setting. + } + // Never redirect on POST. + if (isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] == 'POST')) { + return false; + } + + // Check whether we've skipped the login page already. + // This is here because loginpage_hook is called again during form submission (all of login.php is processed) and + // ?oidc=off is not preserved forcing us to the IdP. + // + // This isn't needed when duallogin is on because $oidc will default to 0 and duallogin is not part of the request. + if ((isset($SESSION->oidc) && $SESSION->oidc == 0)) { + return false; + } + + // If the user is redirectred to the login page immediately after logging out, don't redirect. + $silentloginmodesetting = get_config('auth_oidc', 'silentloginmode'); + $forceredirectsetting = get_config('auth_oidc', 'forceredirect'); + $forceloginsetting = get_config('core', 'forcelogin'); + if ($silentloginmodesetting && $forceredirectsetting && $forceloginsetting && isset($_SERVER['HTTP_REFERER']) && + strpos($_SERVER['HTTP_REFERER'], $CFG->wwwroot) !== false) { + return false; + } + + // Never redirect if requested so. + if ($oidc === 0) { + $SESSION->oidc = $oidc; + return false; + } + // We are off to OIDC land so reset the force in SESSION. + if (isset($SESSION->oidc)) { + unset($SESSION->oidc); + } + + return true; + } + + /** + * Will check if we have to redirect before going to login page + */ + public function pre_loginpage_hook() { + if ($this->should_login_redirect()) { + $this->loginflow->handleredirect(); + } + } + + /** + * Handle requests to the redirect URL. + * + * @return mixed Determined by loginflow. + */ + public function handleredirect() { + return $this->loginflow->handleredirect(); + } + + /** + * Handle OIDC disconnection from Moodle account. + * + * @param bool $justremovetokens If true, just remove the stored OIDC tokens for the user, otherwise revert login methods. + * @param bool $donotremovetokens If true, do not remove tokens when disconnecting. This migrates from a login account to a + * "linked" account. + * @param moodle_url|null $redirect Where to redirect if successful. + * @param moodle_url|null $selfurl The page this is accessed from. Used for some redirects. + * @param null $userid + * @return mixed + */ + public function disconnect($justremovetokens = false, $donotremovetokens = false, ?\moodle_url $redirect = null, + ?\moodle_url $selfurl = null, $userid = null) { + return $this->loginflow->disconnect($justremovetokens, $donotremovetokens, $redirect, $selfurl, $userid); + } + + /** + * This is the primary method that is used by the authenticate_user_login() function in moodlelib.php. + * + * @param string $username The username (with system magic quotes) + * @param string $password The password (with system magic quotes) + * @return bool Authentication success or failure. + */ + public function user_login($username, $password = null) { + global $CFG; + // Short circuit for guest user. + if (!empty($CFG->guestloginbutton) && $username === 'guest' && $password === 'guest') { + return false; + } + return $this->loginflow->user_login($username, $password); + } + + /** + * Read user information from external database and returns it as array(). + * + * @param string $username username + * @return mixed array with no magic quotes or false on error + */ + public function get_userinfo($username) { + return $this->loginflow->get_userinfo($username); + } + + /** + * Indicates if moodle should automatically update internal user + * records with data from external sources using the information + * from get_userinfo() method. + * + * @return bool true means automatically copy data from ext to user table + */ + public function is_synchronised_with_external() { + return true; + } + + /** + * Returns true if this authentication plugin is "internal". + * + * @return bool Whether the plugin uses password hashes from Moodle user table for authentication. + */ + public function is_internal() { + return false; + } + + /** + * Post authentication hook. + * + * This method is called from authenticate_user_login() for all enabled auth plugins. + * + * @param object $user user object, later used for $USER + * @param string $username (with system magic quotes) + * @param string $password plain text password (with system magic quotes) + */ + public function user_authenticated_hook(&$user, $username, $password) { + global $DB; + if (!empty($user) && !empty($user->auth) && $user->auth === 'oidc') { + $tokenrec = $DB->get_record('auth_oidc_token', ['userid' => $user->id]); + if (!empty($tokenrec)) { + // If the token record username is out of sync (ie username changes), update it. + if ($tokenrec->username != $user->username) { + $updatedtokenrec = new \stdClass; + $updatedtokenrec->id = $tokenrec->id; + $updatedtokenrec->username = $user->username; + $DB->update_record('auth_oidc_token', $updatedtokenrec); + $tokenrec = $updatedtokenrec; + } + } else { + // There should always be a token record here, so a failure here means + // the user's token record doesn't yet contain their userid. + $tokenrec = $DB->get_record('auth_oidc_token', ['username' => $username]); + if (!empty($tokenrec)) { + $tokenrec->userid = $user->id; + $updatedtokenrec = new \stdClass; + $updatedtokenrec->id = $tokenrec->id; + $updatedtokenrec->userid = $user->id; + $DB->update_record('auth_oidc_token', $updatedtokenrec); + $tokenrec = $updatedtokenrec; + } + } + + $eventdata = [ + 'objectid' => $user->id, + 'userid' => $user->id, + 'other' => ['username' => $user->username], + ]; + $event = \auth_oidc\event\user_loggedin::create($eventdata); + $event->trigger(); + } + } + + /** + * Log out user from Microsoft 365 if single sign off integration is enabled. + * + * @param stdClass $user + * + * @return bool + */ + public function postlogout_hook($user) { + global $CFG, $DB; + + $singlesignoutsetting = get_config('auth_oidc', 'single_sign_off'); + + if ($singlesignoutsetting) { + $redirect = false; + + if ($user->auth == 'oidc') { + $redirect = true; + } else if (auth_oidc_is_local_365_installed()) { + if ($DB->record_exists('local_o365_objects', ['type' => 'user', 'moodleid' => $user->id])) { + $redirect = true; + } + } + + if ($redirect) { + $logouturl = get_config('auth_oidc', 'logouturi'); + if (!$logouturl) { + $logouturl = 'https://login.microsoftonline.com/organizations/oauth2/logout?post_logout_redirect_uri=' . + urlencode($CFG->wwwroot); + } else { + if (preg_match("/^https:\/\/login.microsoftonline.com\//", $logouturl) && + preg_match("/\/oauth2\/logout$/", $logouturl)) { + $logouturl .= '?post_logout_redirect_uri=' . urlencode($CFG->wwwroot); + } + } + + redirect($logouturl); + } + } + + return true; + } +} diff --git a/auth/oidc/binding_username_claim.php b/auth/oidc/binding_username_claim.php new file mode 100644 index 00000000000..dcb2a1da37d --- /dev/null +++ b/auth/oidc/binding_username_claim.php @@ -0,0 +1,109 @@ +. + +/** + * Manage binding username claim page. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2023 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +use auth_oidc\form\binding_username_claim; + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->libdir . '/adminlib.php'); +require_once($CFG->dirroot . '/auth/oidc/lib.php'); + +require_login(); + +$url = new moodle_url('/auth/oidc/binding_username_claim.php'); +$PAGE->set_url($url); +$PAGE->set_context(context_system::instance()); +$PAGE->set_pagelayout('admin'); +$PAGE->set_heading(get_string('settings_page_binding_username_claim', 'auth_oidc')); +$PAGE->set_title(get_string('settings_page_binding_username_claim', 'auth_oidc')); + +admin_externalpage_setup('auth_oidc_binding_username_claim'); + +require_admin(); + +$form = new binding_username_claim(null); +$formdata = []; + +// Validate auth_oidc_binding_username_claim settings. +$predefinedbindingclaims = ['auto', 'preferred_username', 'email', 'upn', 'unique_name', 'sub', 'oid', 'samaccountname']; + +$oidcconfig = get_config('auth_oidc'); +if (!isset($oidcconfig->bindingusernameclaim)) { + // Bindingusernameclaim is not set, set default value. + $formdata['bindingusernameclaim'] = 'auto'; + $formdata['customclaimname'] = ''; + set_config('bindingusernameclaim', 'auto', 'auth_oidc'); +} else if (!$oidcconfig->bindingusernameclaim) { + $formdata['bindingusernameclaim'] = 'auto'; + $formdata['customclaimname'] = ''; +} else if (in_array($oidcconfig->bindingusernameclaim, $predefinedbindingclaims)) { + $formdata['bindingusernameclaim'] = $oidcconfig->bindingusernameclaim; + $formdata['customclaimname'] = ''; +} else { + $formdata['bindingusernameclaim'] = 'custom'; + $formdata['customclaimname'] = $oidcconfig->bindingusernameclaim; +} + +$form->set_data($formdata); + +if ($form->is_cancelled()) { + redirect($url); +} else if ($fromform = $form->get_data()) { + $configstosave = ['bindingusernameclaim', 'customclaimname']; + + $configchanged = false; + + foreach ($configstosave as $config) { + if (isset($fromform->$config)) { + $existingsetting = $oidcconfig->$config; + if ($fromform->$config != $existingsetting) { + $configchanged = true; + set_config($config, $fromform->$config, 'auth_oidc'); + add_to_config_log($config, $existingsetting, $fromform->$config, 'auth_oidc'); + } + } + } + + if ($configchanged) { + redirect($url, get_string('binding_username_claim_updated', 'auth_oidc')); + } else { + redirect($url); + } +} + +$existingclaims = auth_oidc_get_existing_claims(); + +echo $OUTPUT->header(); + +echo $OUTPUT->heading(get_string('binding_username_claim_heading', 'auth_oidc')); +$bindingusernametoolurl = new moodle_url('/auth/oidc/change_binding_username_claim_tool.php'); +echo html_writer::tag('p', get_string('binding_username_claim_description', 'auth_oidc', $bindingusernametoolurl->out())); +if ($existingclaims) { + echo html_writer::tag('p', get_string('binding_username_claim_description_existing_claims', 'auth_oidc', + implode(' / ', $existingclaims))); +} + +$form->display(); + +echo $OUTPUT->footer(); diff --git a/auth/oidc/change_binding_username_claim_tool.php b/auth/oidc/change_binding_username_claim_tool.php new file mode 100644 index 00000000000..491f752097a --- /dev/null +++ b/auth/oidc/change_binding_username_claim_tool.php @@ -0,0 +1,128 @@ +. + +/** + * Change binding username claim tool page. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2023 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +use auth_oidc\form\change_binding_username_claim_tool_form1; +use auth_oidc\form\change_binding_username_claim_tool_form2; +use auth_oidc\preview; +use auth_oidc\process; + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->libdir . '/adminlib.php'); +require_once($CFG->libdir . '/csvlib.class.php'); + +require_login(); + +$url = new moodle_url('/auth/oidc/change_binding_username_claim_tool.php'); +$PAGE->set_url($url); +$PAGE->set_context(context_system::instance()); +$PAGE->set_pagelayout('admin'); +$PAGE->set_heading(get_string('settings_page_change_binding_username_claim_tool', 'auth_oidc')); +$PAGE->set_title(get_string('settings_page_change_binding_username_claim_tool', 'auth_oidc')); + +admin_externalpage_setup('auth_oidc_change_binding_username_claim_tool'); + +require_admin(); + +$iid = optional_param('iid', '', PARAM_INT); +$previewrows = optional_param('previewrows', 10, PARAM_INT); + +core_php_time_limit::raise(60 * 60); // 1 hour should be enough. +raise_memory_limit(MEMORY_HUGE); + +if (empty($iid)) { + $form1 = new change_binding_username_claim_tool_form1(); + if ($formdata = $form1->get_data()) { + $iid = csv_import_reader::get_new_iid('changebindingusernameclaimtool'); + $cir = new csv_import_reader($iid, 'changebindingusernameclaimtool'); + + $content = $form1->get_file_content('usernamefile'); + + $readcount = $cir->load_csv_content($content, $formdata->encoding, $formdata->delimiter_name); + $csvloaderror = $cir->get_error(); + unset($content); + + if (!is_null($csvloaderror)) { + throw new moodle_exception('csvloaderror', '', $url, $csvloaderror); + } + } else { + echo $OUTPUT->header(); + + echo $OUTPUT->heading(get_string('change_binding_username_claim_tool', 'auth_oidc')); + $bindingusernameclaimurl = new moodle_url('/auth/oidc/binding_username_claim.php'); + echo html_writer::tag('p', get_string('change_binding_username_claim_tool_description', 'auth_oidc', + $bindingusernameclaimurl->out())); + + $form1->display(); + + echo $OUTPUT->footer(); + exit; + } +} else { + $cir = new csv_import_reader($iid, 'changebindingusernameclaimtool'); +} + +// Test if columns ok. +$process = new process($cir); +$filecolumns = $process->get_file_columns(); + +$mform2 = new change_binding_username_claim_tool_form2(null, + ['columns' => $filecolumns, 'data' => ['iid' => $iid, 'previewrows' => $previewrows]]); + +// If a file has been uploaded, then process it. +if ($mform2->is_cancelled()) { + $cir->cleanup(true); + redirect($url); +} else if ($formdata = $mform2->get_data()) { + // Print the header. + echo $OUTPUT->header(); + echo $OUTPUT->heading(get_string('change_binding_username_claim_tool_result', 'auth_oidc')); + + $process->set_form_data($formdata); + $process->process(); + + echo $OUTPUT->box_start('boxwidthnarrow boxaligncenter generalbox', 'uploadresults'); + echo html_writer::tag('p', join('
', $process->get_stats())); + echo $OUTPUT->box_end(); + + echo $OUTPUT->footer(); + exit; +} + +// Print the header. +echo $OUTPUT->header(); + +echo $OUTPUT->heading(get_string('change_binding_username_claim_tool', 'auth_oidc')); + +$table = new preview($cir, $filecolumns, $previewrows); + +echo html_writer::tag('div', html_writer::table($table), ['class' => 'flexible-wrap']); + +if ($table->get_no_error()) { + $mform2->display(); +} + +echo $OUTPUT->footer(); + +exit; diff --git a/auth/oidc/classes/adminsetting/auth_oidc_admin_setting_iconselect.php b/auth/oidc/classes/adminsetting/auth_oidc_admin_setting_iconselect.php new file mode 100644 index 00000000000..1fa8c842507 --- /dev/null +++ b/auth/oidc/classes/adminsetting/auth_oidc_admin_setting_iconselect.php @@ -0,0 +1,123 @@ +. + +/** + * Definition of an icon selector admin setting control. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\adminsetting; + +/** + * Choose an icon for the identity provider entry on the login page. + */ +class auth_oidc_admin_setting_iconselect extends \admin_setting { + /** @var array The stock icons. */ + protected $choices = []; + + /** + * Constructor. + * + * @param string $name Name of the setting. + * @param string $visiblename Visible name of the setting. + * @param string $description Description of the setting. + * @param array $defaultsetting Default value. + * @param array $choices Array of icon choices. + */ + public function __construct($name, $visiblename, $description, $defaultsetting, $choices) { + $this->choices = $choices; + parent::__construct($name, $visiblename, $description, $defaultsetting, $choices); + } + + /** + * Return the setting + * + * @return mixed returns config if successful else null + */ + public function get_setting() { + return $this->config_read($this->name); + } + + /** + * Save a setting + * + * @param string $data + * + * @return string empty of error string + */ + public function write_setting($data) { + // Validate incoming data. + $found = false; + foreach ($this->choices as $icon) { + $id = $icon['component'] . ':' . $icon['pix']; + if ($data === $id) { + $found = true; + break; + } + } + + // Invalid value received, ignore it. + if ($found !== true) { + return ''; + } + + return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin')); + } + + /** + * Get admin setting HTML. + * + * @param mixed $data Saved data. + * @param string $query + * + * @return string The setting HTML. + */ + public function output_html($data, $query = '') { + global $CFG, $OUTPUT; + $attrs = ['type' => 'text/css', 'rel' => 'stylesheet', + 'href' => new \moodle_url('/auth/oidc/classes/adminsetting/iconselect.css')]; + $html = \html_writer::empty_tag('link', $attrs); + $html .= \html_writer::start_tag('div', ['style' => 'max-width: 390px']); + $selected = (!empty($data)) ? $data : $this->defaultsetting; + foreach ($this->choices as $icon) { + $id = $icon['component'] . ':' . $icon['pix']; + $iconhtml = $OUTPUT->image_icon($icon['pix'], $icon['alt'], $icon['component']); + $inputattrs = [ + 'type' => 'radio', + 'id' => $id, + 'name' => $this->get_full_name(), + 'value' => $id, + 'class' => 'iconselect', + ]; + + if ($id === $selected) { + $inputattrs['checked'] = 'checked'; + } + $html .= \html_writer::empty_tag('input', $inputattrs); + $labelattrs = [ + 'class' => 'iconselect', + ]; + $html .= \html_writer::label($iconhtml, $id, true, $labelattrs); + } + $html .= \html_writer::end_tag('div'); + + return format_admin_setting($this, $this->visiblename, $html, $this->description, true, '', null, $query); + } +} diff --git a/auth/oidc/classes/adminsetting/auth_oidc_admin_setting_label.php b/auth/oidc/classes/adminsetting/auth_oidc_admin_setting_label.php new file mode 100644 index 00000000000..18917d0a275 --- /dev/null +++ b/auth/oidc/classes/adminsetting/auth_oidc_admin_setting_label.php @@ -0,0 +1,83 @@ +. + +/** + * Definition of a label admin setting control. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2021 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\adminsetting; + +use admin_setting; + +/** + * Display a static text. + */ +class auth_oidc_admin_setting_label extends admin_setting { + /** + * @var string $label The label for display purposes. + */ + private $label; + + /** + * Constructor. + * + * @param string $name The setting name. + * @param string $label The label to display. + * @param string $visiblename The visible name for the setting. + * @param string $description A description of the setting. + */ + public function __construct($name, $label, $visiblename, $description) { + parent::__construct($name, $visiblename, $description, ''); + $this->label = $label; + } + + /** + * No settings to get. + * + * @return bool + */ + public function get_setting() { + return true; + } + + /** + * Nothing to write. + * + * @param mixed $data + * + * @return string + */ + public function write_setting($data) { + return ''; + } + + /** + * Output the setting. + * + * @param mixed $data + * @param string $query + * + * @return string + */ + public function output_html($data, $query = '') { + return format_admin_setting($this, $this->label, $this->visiblename, $this->description, false); + } +} diff --git a/auth/oidc/classes/adminsetting/auth_oidc_admin_setting_loginflow.php b/auth/oidc/classes/adminsetting/auth_oidc_admin_setting_loginflow.php new file mode 100644 index 00000000000..7a7dcb49c02 --- /dev/null +++ b/auth/oidc/classes/adminsetting/auth_oidc_admin_setting_loginflow.php @@ -0,0 +1,97 @@ +. + +/** + * Definition of login flow selector admin setting control. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\adminsetting; + +/** + * Displays the redirect URI for easier config. + */ +class auth_oidc_admin_setting_loginflow extends \admin_setting { + /** @var array Array of valid login flow types. */ + protected $flowtypes = ['authcode', 'rocreds']; + + /** + * Return the setting + * + * @return mixed returns config if successful else null + */ + public function get_setting() { + return $this->config_read($this->name); + } + + /** + * Save a setting + * + * @param string $data + * @return string empty of error string + */ + public function write_setting($data) { + if (!in_array($data, $this->flowtypes)) { + // Ignore invalid settings. + return ''; + } + + return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin')); + } + + /** + * Returns XHTML select field and wrapping div(s) + * + * @see output_select_html() + * + * @param string $data the option to show as selected + * @param string $query + * @return string XHTML field and wrapping div + */ + public function output_html($data, $query = '') { + $html = ''; + $baseid = $this->get_id(); + $inputname = $this->get_full_name(); + + foreach ($this->flowtypes as $flowtype) { + $html .= \html_writer::start_div(); + $flowtypeid = $baseid.'_'.$flowtype; + $radioattrs = [ + 'type' => 'radio', + 'name' => $inputname, + 'id' => $flowtypeid, + 'value' => $flowtype, + ]; + if ($data === $flowtype || (empty($data) && $flowtype === $this->get_defaultsetting())) { + $radioattrs['checked'] = 'checked'; + } + $typename = get_string('cfg_loginflow_'.$flowtype, 'auth_oidc'); + $typedesc = get_string('cfg_loginflow_'.$flowtype.'_desc', 'auth_oidc'); + $html .= \html_writer::empty_tag('input', $radioattrs); + $html .= \html_writer::label($typename, $flowtypeid, false); + $html .= '
'; + $html .= \html_writer::span($typedesc); + $html .= '

'; + $html .= \html_writer::end_div(); + } + + return format_admin_setting($this, $this->visiblename, $html, $this->description, true, '', null, $query); + } +} diff --git a/auth/oidc/classes/adminsetting/auth_oidc_admin_setting_redirecturi.php b/auth/oidc/classes/adminsetting/auth_oidc_admin_setting_redirecturi.php new file mode 100644 index 00000000000..9473805a674 --- /dev/null +++ b/auth/oidc/classes/adminsetting/auth_oidc_admin_setting_redirecturi.php @@ -0,0 +1,94 @@ +. + +/** + * Definition of a redirect URL admin setting control. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\adminsetting; + +use auth_oidc\utils; + +/** + * Displays the redirect URI for easier config. + */ +class auth_oidc_admin_setting_redirecturi extends \admin_setting { + /** + * @var string $url The redirect URL for the configuration. + */ + private $url; + + /** + * Constructor. + * + * @param string $name The setting name. + * @param string $heading The setting heading. + * @param string $description The setting description. + * @param string $url The redirect URL. + */ + public function __construct($name, $heading, $description, $url) { + $this->nosave = true; + $this->url = $url; + parent::__construct($name, $heading, $description, ''); + } + + /** + * Always returns true because we have no real setting. + * + * @return bool Always returns true + */ + public function get_setting() { + return true; + } + + /** + * Always returns true because we have no real setting. + * + * @return bool Always returns true + */ + public function get_defaultsetting() { + return true; + } + + /** + * Never write settings. + * + * @param mixed $data + * @return string Always returns an empty string. + */ + public function write_setting($data) { + return ''; + } + + /** + * Returns an HTML string for the redirect uri display. + * + * @param mixed $data + * @param string $query + * @return string Returns an HTML string. + */ + public function output_html($data, $query = '') { + $redirecturl = utils::get_redirecturl(); + $redirecturl = $this->url; + $html = \html_writer::tag('h5', $redirecturl); + return format_admin_setting($this, $this->visiblename, $html, $this->description, true, '', null, $query); + } +} diff --git a/auth/oidc/classes/adminsetting/iconselect.css b/auth/oidc/classes/adminsetting/iconselect.css new file mode 100644 index 00000000000..6fe4121d479 --- /dev/null +++ b/auth/oidc/classes/adminsetting/iconselect.css @@ -0,0 +1,26 @@ +label.iconselect { + display: inline-block !important; + padding: 0 !important; + margin: 5px; +} +label.iconselect img { + width: 25px; + height: 25px; + padding: 10px; +} +input.iconselect { + display: none; +} +input[type="radio"].iconselect:checked + label.iconselect { + outline: 1px solid #007fec; +} +body.ie input.iconselect { + display: inline-block; +} +body.ie label.iconselect { + margin-left: 0; + margin-right: 20px; +} +body.ie label.iconselect img { + padding: 5px; +} \ No newline at end of file diff --git a/auth/oidc/classes/event/action_failed.php b/auth/oidc/classes/event/action_failed.php new file mode 100644 index 00000000000..ff99cb06e2a --- /dev/null +++ b/auth/oidc/classes/event/action_failed.php @@ -0,0 +1,60 @@ +. + +/** + * An event when something wrong happened, and debug message needs to be logged. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\event; + +/** + * Event fired whenever we need to record a debug message. + */ +class action_failed extends \core\event\base { + /** + * Return localised event name. + * + * @return string + */ + public static function get_name() { + return get_string('event_debug', 'auth_oidc'); + } + + /** + * Returns non-localised event description with id's for admin use only. + * + * @return string + */ + public function get_description() { + return $this->data['other']; + } + + /** + * Init method. + * + * @return void + */ + protected function init() { + $this->context = \context_system::instance(); + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } +} diff --git a/auth/oidc/classes/event/user_authed.php b/auth/oidc/classes/event/user_authed.php new file mode 100644 index 00000000000..97bf2a8259a --- /dev/null +++ b/auth/oidc/classes/event/user_authed.php @@ -0,0 +1,60 @@ +. + +/** + * A user authenticated with IODC event. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\event; + +/** + * Event fired when a user authenticated with OIDC, but does not log in. + */ +class user_authed extends \core\event\base { + /** + * Return localised event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventuserauthed', 'auth_oidc'); + } + + /** + * Returns non-localised event description with id's for admin use only. + * + * @return string + */ + public function get_description() { + return "A user authorized with OpenID Connect, but did not associate with a user account."; + } + + /** + * Init method. + * + * @return void + */ + protected function init() { + $this->context = \context_system::instance(); + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } +} diff --git a/auth/oidc/classes/event/user_connected.php b/auth/oidc/classes/event/user_connected.php new file mode 100644 index 00000000000..3f543a9968b --- /dev/null +++ b/auth/oidc/classes/event/user_connected.php @@ -0,0 +1,61 @@ +. + +/** + * A user ocnnects to OpenID Connect event. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\event; + +/** + * Fired when a user connects to OpenID Connect. + */ +class user_connected extends \core\event\base { + /** + * Return localised event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventuserconnected', 'auth_oidc'); + } + + /** + * Returns non-localised event description with id's for admin use only. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' has switched to using OpenID Connect (auth plugin 'auth_oidc')."; + } + + /** + * Init method. + * + * @return void + */ + protected function init() { + $this->context = \context_system::instance(); + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_OTHER; + $this->data['objecttable'] = 'user'; + } +} diff --git a/auth/oidc/classes/event/user_created.php b/auth/oidc/classes/event/user_created.php new file mode 100644 index 00000000000..74c8626da3c --- /dev/null +++ b/auth/oidc/classes/event/user_created.php @@ -0,0 +1,61 @@ +. + +/** + * OIDC user created event. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\event; + +/** + * Event fired when OIDC creates a new user. + */ +class user_created extends \core\event\base { + /** + * Return localised event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventusercreated', 'auth_oidc'); + } + + /** + * Returns non-localised event description with id's for admin use only. + * + * @return string + */ + public function get_description() { + return "A user (user id '{$this->userid}') was creatd using the OpenID Connect authentication plugin."; + } + + /** + * Init method. + * + * @return void + */ + protected function init() { + $this->context = \context_system::instance(); + $this->data['crud'] = 'c'; + $this->data['edulevel'] = self::LEVEL_OTHER; + $this->data['objecttable'] = 'user'; + } +} diff --git a/auth/oidc/classes/event/user_disconnected.php b/auth/oidc/classes/event/user_disconnected.php new file mode 100644 index 00000000000..5fe9ce37da1 --- /dev/null +++ b/auth/oidc/classes/event/user_disconnected.php @@ -0,0 +1,61 @@ +. + +/** + * A user disconnected from OpenID Connect event. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\event; + +/** + * Fired when a user disconnects from OpenID Connect. + */ +class user_disconnected extends \core\event\base { + /** + * Return localised event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventuserdisconnected', 'auth_oidc'); + } + + /** + * Returns non-localised event description with id's for admin use only. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' has disconnected from OpenID Connect (auth plugin 'auth_oidc')."; + } + + /** + * Init method. + * + * @return void + */ + protected function init() { + $this->context = \context_system::instance(); + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_OTHER; + $this->data['objecttable'] = 'user'; + } +} diff --git a/auth/oidc/classes/event/user_loggedin.php b/auth/oidc/classes/event/user_loggedin.php new file mode 100644 index 00000000000..1c5c0342a72 --- /dev/null +++ b/auth/oidc/classes/event/user_loggedin.php @@ -0,0 +1,61 @@ +. + +/** + * A user uses OIDC logged in event. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\event; + +/** + * Fired when a user uses OIDC to log in. + */ +class user_loggedin extends \core\event\base { + /** + * Return localised event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventuserloggedin', 'auth_oidc'); + } + + /** + * Returns non-localised event description with id's for admin use only. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' has logged in using OpenID Connect (auth plugin 'auth_oidc')."; + } + + /** + * Init method. + * + * @return void + */ + protected function init() { + $this->context = \context_system::instance(); + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_OTHER; + $this->data['objecttable'] = 'user'; + } +} diff --git a/auth/oidc/classes/event/user_rename_attempt.php b/auth/oidc/classes/event/user_rename_attempt.php new file mode 100644 index 00000000000..86bb7971f80 --- /dev/null +++ b/auth/oidc/classes/event/user_rename_attempt.php @@ -0,0 +1,64 @@ +. + +/** + * A Moodle user rename attempt event. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\event; + +use context_system; +use core\event\base; + +/** + * Fired when a user attempts to change their username from the auth_oidc plugin. + */ +class user_rename_attempt extends base { + /** + * Return localised event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventuserrenameattempt', 'auth_oidc'); + } + + /** + * Returns non-localised event description with id's for admin use only. + * + * @return string + */ + public function get_description() { + return "The auth_oidc plugin attempts to change the username of the user with id '$this->userid'."; + } + + /** + * Init method. + * + * @return void + */ + protected function init() { + $this->context = context_system::instance(); + $this->data['crud'] = 'u'; + $this->data['edulevel'] = self::LEVEL_OTHER; + $this->data['objecttable'] = 'user'; + } +} diff --git a/auth/oidc/classes/form/application.php b/auth/oidc/classes/form/application.php new file mode 100644 index 00000000000..a3769cf9b93 --- /dev/null +++ b/auth/oidc/classes/form/application.php @@ -0,0 +1,281 @@ +. + +/** + * Authentication and endpoints configuration form. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2022 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\form; + +use moodleform; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/auth/oidc/lib.php'); + +/** + * Class authentication_and_endpoints represents the form on the authentication and endpoints configuration page. + */ +class application extends moodleform { + /** + * Form definition. + * + * @return void + */ + protected function definition() { + $mform =& $this->_form; + + // Basic settings header. + $mform->addElement('header', 'basic', get_string('settings_section_basic', 'auth_oidc')); + + // IdP type. + $idptypeoptions = [ + AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID => get_string('idp_type_microsoft_entra_id', 'auth_oidc'), + AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM => get_string('idp_type_microsoft_identity_platform', 'auth_oidc'), + AUTH_OIDC_IDP_TYPE_OTHER => get_string('idp_type_other', 'auth_oidc'), + ]; + $mform->addElement('select', 'idptype', auth_oidc_config_name_in_form('idptype'), $idptypeoptions); + $mform->addElement('static', 'idptype_help', '', get_string('idptype_help', 'auth_oidc')); + + // Client ID. + $mform->addElement('text', 'clientid', auth_oidc_config_name_in_form('clientid'), ['size' => 40]); + $mform->setType('clientid', PARAM_TEXT); + $mform->addElement('static', 'clientid_help', '', get_string('clientid_help', 'auth_oidc')); + $mform->addRule('clientid', null, 'required', null, 'client'); + + // Authentication header. + $mform->addElement('header', 'authentication', get_string('settings_section_authentication', 'auth_oidc')); + $mform->setExpanded('authentication'); + + // Authentication method depending on IdP type. + $authmethodoptions = [ + AUTH_OIDC_AUTH_METHOD_SECRET => get_string('auth_method_secret', 'auth_oidc'), + ]; + if (isset($this->_customdata['oidcconfig']->idptype) && + $this->_customdata['oidcconfig']->idptype == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) { + $authmethodoptions[AUTH_OIDC_AUTH_METHOD_CERTIFICATE] = get_string('auth_method_certificate', 'auth_oidc'); + } + $mform->addElement('select', 'clientauthmethod', auth_oidc_config_name_in_form('clientauthmethod'), $authmethodoptions); + $mform->setDefault('clientauthmethod', AUTH_OIDC_AUTH_METHOD_SECRET); + $mform->addElement('static', 'clientauthmethod_help', '', get_string('clientauthmethod_help', 'auth_oidc')); + + // Secret. + $mform->addElement('text', 'clientsecret', auth_oidc_config_name_in_form('clientsecret'), ['size' => 60]); + $mform->setType('clientsecret', PARAM_TEXT); + $mform->disabledIf('clientsecret', 'clientauthmethod', 'neq', AUTH_OIDC_AUTH_METHOD_SECRET); + $mform->addElement('static', 'clientsecret_help', '', get_string('clientsecret_help', 'auth_oidc')); + + // Certificate source. + $mform->addElement('select', 'clientcertsource', auth_oidc_config_name_in_form('clientcertsource'), [ + AUTH_OIDC_AUTH_CERT_SOURCE_TEXT => get_string('cert_source_text', 'auth_oidc'), + AUTH_OIDC_AUTH_CERT_SOURCE_FILE => get_string('cert_source_path', 'auth_oidc'), + ]); + $mform->setDefault('clientcertsource', 0); + $mform->disabledIf('clientcertsource', 'clientauthmethod', 'neq', AUTH_OIDC_AUTH_METHOD_CERTIFICATE); + $mform->addElement('static', 'clientcertsource_help', '', get_string('clientcertsource_help', 'auth_oidc')); + + // Certificate private key. + $mform->addElement('textarea', 'clientprivatekey', auth_oidc_config_name_in_form('clientprivatekey'), + ['rows' => 10, 'cols' => 80, 'class' => 'cert_textarea']); + $mform->setType('clientprivatekey', PARAM_TEXT); + $mform->disabledIf('clientprivatekey', 'clientauthmethod', 'neq', AUTH_OIDC_AUTH_METHOD_CERTIFICATE); + $mform->disabledIf('clientprivatekey', 'clientcertsource', 'neq', AUTH_OIDC_AUTH_CERT_SOURCE_TEXT); + $mform->addElement('static', 'clientprivatekey_help', '', get_string('clientprivatekey_help', 'auth_oidc')); + + // Certificate certificate. + $mform->addElement('textarea', 'clientcert', auth_oidc_config_name_in_form('clientcert'), + ['rows' => 10, 'cols' => 80, 'class' => 'cert_textarea']); + $mform->setType('clientcert', PARAM_TEXT); + $mform->disabledIf('clientcert', 'clientauthmethod', 'neq', AUTH_OIDC_AUTH_METHOD_CERTIFICATE); + $mform->disabledIf('clientcert', 'clientcertsource', 'neq', AUTH_OIDC_AUTH_CERT_SOURCE_TEXT); + $mform->addElement('static', 'clientcert_help', '', get_string('clientcert_help', 'auth_oidc')); + + // Certificate file of private key. + $mform->addElement('text', 'clientprivatekeyfile', auth_oidc_config_name_in_form('clientprivatekeyfile'), ['size' => 60]); + $mform->setType('clientprivatekeyfile', PARAM_FILE); + $mform->disabledIf('clientprivatekeyfile', 'clientauthmethod', 'neq', AUTH_OIDC_AUTH_METHOD_CERTIFICATE); + $mform->disabledIf('clientprivatekeyfile', 'clientcertsource', 'neq', AUTH_OIDC_AUTH_CERT_SOURCE_FILE); + $mform->addElement('static', 'clientprivatekeyfile_help', '', get_string('clientprivatekeyfile_help', 'auth_oidc')); + + // Certificate file of certificate or public key. + $mform->addElement('text', 'clientcertfile', auth_oidc_config_name_in_form('clientcertfile'), ['size' => 60]); + $mform->setType('clientcertfile', PARAM_FILE); + $mform->disabledIf('clientcertfile', 'clientauthmethod', 'neq', AUTH_OIDC_AUTH_METHOD_CERTIFICATE); + $mform->disabledIf('clientcertfile', 'clientcertsource', 'neq', AUTH_OIDC_AUTH_CERT_SOURCE_FILE); + $mform->addElement('static', 'clientcertfile_help', '', get_string('clientcertfile_help', 'auth_oidc')); + + // Certificate file passphrase. + $mform->addElement('text', 'clientcertpassphrase', auth_oidc_config_name_in_form('clientcertpassphrase'), ['size' => 60]); + $mform->setType('clientcertpassphrase', PARAM_TEXT); + $mform->disabledIf('clientcertpassphrase', 'clientauthmethod', 'neq', AUTH_OIDC_AUTH_METHOD_CERTIFICATE); + $mform->addElement('static', 'clientcertpassphrase_help', '', get_string('clientcertpassphrase_help', 'auth_oidc')); + + // Endpoints header. + $mform->addElement('header', 'endpoints', get_string('settings_section_endpoints', 'auth_oidc')); + $mform->setExpanded('endpoints'); + + // Authorization endpoint. + $mform->addElement('text', 'authendpoint', auth_oidc_config_name_in_form('authendpoint'), ['size' => 60]); + $mform->setType('authendpoint', PARAM_URL); + $mform->setDefault('authendpoint', 'https://login.microsoftonline.com/organizations/oauth2/authorize'); + $mform->addElement('static', 'authendpoint_help', '', get_string('authendpoint_help', 'auth_oidc')); + $mform->addRule('authendpoint', null, 'required', null, 'client'); + + // Token endpoint. + $mform->addElement('text', 'tokenendpoint', auth_oidc_config_name_in_form('tokenendpoint'), ['size' => 60]); + $mform->setType('tokenendpoint', PARAM_URL); + $mform->setDefault('tokenendpoint', 'https://login.microsoftonline.com/organizations/oauth2/token'); + $mform->addElement('static', 'tokenendpoint_help', '', get_string('tokenendpoint_help', 'auth_oidc')); + $mform->addRule('tokenendpoint', null, 'required', null, 'client'); + + // Other parameters header. + $mform->addElement('header', 'otherparams', get_string('settings_section_other_params', 'auth_oidc')); + $mform->setExpanded('otherparams'); + + // Resource. + $mform->addElement('text', 'oidcresource', auth_oidc_config_name_in_form('oidcresource'), ['size' => 60]); + $mform->setType('oidcresource', PARAM_TEXT); + $mform->setDefault('oidcresource', 'https://graph.microsoft.com'); + $mform->addElement('static', 'oidcresource_help', '', get_string('oidcresource_help', 'auth_oidc')); + + // Scope. + $mform->addElement('text', 'oidcscope', auth_oidc_config_name_in_form('oidcscope'), ['size' => 60]); + $mform->setType('oidcscope', PARAM_TEXT); + $mform->setDefault('oidcscope', 'openid profile email'); + $mform->addElement('static', 'oidcscope_help', '', get_string('oidcscope_help', 'auth_oidc')); + + // Secret expiry notifications recipients. + if (auth_oidc_is_local_365_installed()) { + $mform->addElement('header', 'secretexpirynotification', + get_string('settings_section_secret_expiry_notification', 'auth_oidc')); + $mform->setExpanded('secretexpirynotification'); + + $mform->addElement('text', 'secretexpiryrecipients', auth_oidc_config_name_in_form('secretexpiryrecipients'), + ['size' => 256]); + $mform->setType('secretexpiryrecipients', PARAM_TEXT); + $mform->disabledIf('secretexpiryrecipients', 'clientauthmethod', 'neq', AUTH_OIDC_AUTH_METHOD_SECRET); + $mform->disabledIf('secretexpiryrecipients', 'idptype', 'eq', AUTH_OIDC_IDP_TYPE_OTHER); + + $mform->addElement('static', 'secretexpiryrecipients_help', '', get_string('secretexpiryrecipients_help', 'auth_oidc')); + } + + // Save buttons. + $this->add_action_buttons(); + } + + /** + * Additional validate rules. + * + * @param array $data Submitted data for validation. + * @param array $files Uploaded files for validation. + * @return array An array of validation errors, if any. + */ + public function validation($data, $files) { + $errors = parent::validation($data, $files); + + if (!isset($data['clientauthmethod'])) { + $data['clientauthmethod'] = $this->optional_param('clientauthmethod', AUTH_OIDC_AUTH_METHOD_SECRET, PARAM_INT); + } + + // Validate "clientauthmethod" according to "idptype". + switch ($data['idptype']) { + case AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID: + case AUTH_OIDC_IDP_TYPE_OTHER: + if ($data['clientauthmethod'] != AUTH_OIDC_AUTH_METHOD_SECRET) { + $errors['clientauthmethod'] = get_string('error_invalid_client_authentication_method', 'auth_oidc'); + } + break; + case AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM: + if (!in_array($data['clientauthmethod'], [AUTH_OIDC_AUTH_METHOD_SECRET, AUTH_OIDC_AUTH_METHOD_CERTIFICATE])) { + $errors['clientauthmethod'] = get_string('error_invalid_client_authentication_method', 'auth_oidc'); + } + break; + } + + // Validate authentication variables. + switch ($data['clientauthmethod']) { + case AUTH_OIDC_AUTH_METHOD_SECRET: + if (empty(trim($data['clientsecret']))) { + $errors['clientsecret'] = get_string('error_empty_client_secret', 'auth_oidc'); + } + break; + case AUTH_OIDC_AUTH_METHOD_CERTIFICATE: + switch ($data['clientcertsource']) { + case AUTH_OIDC_AUTH_CERT_SOURCE_TEXT: + if (empty(trim($data['clientprivatekey']))) { + $errors['clientprivatekey'] = get_string('error_empty_client_private_key', 'auth_oidc'); + } + if (empty(trim($data['clientcert']))) { + $errors['clientcert'] = get_string('error_empty_client_cert', 'auth_oidc'); + } + break; + case AUTH_OIDC_AUTH_CERT_SOURCE_FILE: + if (empty(trim($data['clientprivatekeyfile']))) { + $errors['clientprivatekeyfile'] = get_string('error_empty_client_private_key_file', 'auth_oidc'); + } + if (empty(trim($data['clientcertfile']))) { + $errors['clientcertfile'] = get_string('error_empty_client_cert_file', 'auth_oidc'); + } + break; + } + break; + } + + // Validate endpoints. + if (in_array($data['idptype'], [AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID, AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM])) { + // Ensure authendpoint version matches IdP type. + $authendpointidptype = auth_oidc_determine_endpoint_version($data['authendpoint']); + if ($authendpointidptype != $data['idptype']) { + $errors['authendpoint'] = get_string('error_endpoint_mismatch_auth_endpoint', 'auth_oidc'); + } + + // Ensure tokenendpoint version matches IdP type. + $tokenendpointtype = auth_oidc_determine_endpoint_version($data['tokenendpoint']); + if ($tokenendpointtype != $data['idptype']) { + $errors['tokenendpoint'] = get_string('error_endpoint_mismatch_token_endpoint', 'auth_oidc'); + } + + // If "certificate" authentication method is used, ensure tenant specific endpoints are used. + if ($data['idptype'] == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM && + $data['clientauthmethod'] == AUTH_OIDC_AUTH_METHOD_CERTIFICATE) { + if (strpos($data['authendpoint'], '/common/') !== false || + strpos($data['authendpoint'], '/organizations/') !== false || + strpos($data['authendpoint'], '/consumers/') !== false) { + $errors['authendpoint'] = get_string('error_tenant_specific_endpoint_required', 'auth_oidc'); + } + if (strpos($data['tokenendpoint'], '/common/') !== false || + strpos($data['tokenendpoint'], '/organizations/') !== false || + strpos($data['tokenendpoint'], '/consumers/') !== false) { + $errors['tokenendpoint'] = get_string('error_tenant_specific_endpoint_required', 'auth_oidc'); + } + } + } + + // Validate oidcresource. + if (in_array($data['idptype'], [AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID, AUTH_OIDC_IDP_TYPE_OTHER])) { + if (empty(trim($data['oidcresource']))) { + $errors['oidcresource'] = get_string('error_empty_oidcresource', 'auth_oidc'); + } + } + + return $errors; + } +} diff --git a/auth/oidc/classes/form/binding_username_claim.php b/auth/oidc/classes/form/binding_username_claim.php new file mode 100644 index 00000000000..9852d69d7d2 --- /dev/null +++ b/auth/oidc/classes/form/binding_username_claim.php @@ -0,0 +1,138 @@ +. + +/** + * Manage binding username claim form. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2022 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\form; + +use moodle_exception; +use moodleform; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/auth/oidc/lib.php'); + +/** + * Class bindingusernameclaim represents the form on the binding username claim configuration page. + */ +class binding_username_claim extends moodleform { + /** + * Option for setting a non-Microsoft IdP. + */ + const OPTION_SET_NON_MS_IDP = 1; + + /** + * Option for setting a Microsoft IdP without user sync. + */ + const OPTION_SET_MS_NO_USER_SYNC = 2; + + /** + * Option for setting a Microsoft IdP with user sync enabled. + */ + const OPTION_SET_MS_WITH_USER_SYNC = 3; + + /** @var int */ + private $optionset = 0; + + /** + * Form definition. + * + * @return void + */ + protected function definition() { + $mform =& $this->_form; + + // Binding username claim. + $idptype = get_config('auth_oidc', 'idptype'); + $bindingusernameoptions = []; + switch ($idptype) { + case AUTH_OIDC_IDP_TYPE_OTHER: + $this->optionset = self::OPTION_SET_NON_MS_IDP; + $descriptionidentifier = 'binding_username_claim_help_non_ms'; + $bindingusernameoptions = [ + 'auto' => get_string('binding_username_auto', 'auth_oidc'), // Use default logic. + 'preferred_username' => 'preferred_username', + 'email' => 'email', + 'unique_name' => 'unique_name', + 'sub' => 'sub', + 'samaccountname' => 'samaccountname', + 'custom' => get_string('binding_username_custom', 'auth_oidc'), // Custom value. + ]; + break; + case AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM: + case AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID: + if (auth_oidc_is_local_365_installed() && auth_oidc_is_user_sync_enabled()) { + $this->optionset = self::OPTION_SET_MS_WITH_USER_SYNC; + $descriptionidentifier = 'binding_username_claim_help_ms_with_user_sync'; + $bindingusernameoptions = [ + 'auto' => get_string('binding_username_auto', 'auth_oidc'), // Use default logic. + 'email' => 'email', + 'upn' => 'upn', + 'oid' => 'oid', + 'samaccountname' => 'samaccountname', + ]; + } else { + $this->optionset = self::OPTION_SET_MS_NO_USER_SYNC; + $descriptionidentifier = 'binding_username_claim_help_ms_no_user_sync'; + $bindingusernameoptions = [ + 'auto' => get_string('binding_username_auto', 'auth_oidc'), // Use default logic. + 'preferred_username' => 'preferred_username', + 'email' => 'email', + 'upn' => 'upn', + 'unique_name' => 'unique_name', + 'oid' => 'oid', + 'sub' => 'sub', + 'samaccountname' => 'samaccountname', + 'custom' => get_string('binding_username_custom', 'auth_oidc'), // Custom value. + ]; + } + break; + } + + if (empty($bindingusernameoptions)) { + throw new moodle_exception('missing_idp_type', 'auth_oidc'); + } + + $mform->addElement( + 'select', + 'bindingusernameclaim', + auth_oidc_config_name_in_form('bindingusernameclaim'), + $bindingusernameoptions + ); + $mform->setDefault('bindingusernameclaim', 'auto'); + $mform->addElement('static', 'bindingusernameclaim_description', '', get_string($descriptionidentifier, 'auth_oidc')); + + // Custom claim name. + if ($this->optionset == self::OPTION_SET_NON_MS_IDP || $this->optionset == self::OPTION_SET_MS_NO_USER_SYNC) { + $mform->addElement('text', 'customclaimname', auth_oidc_config_name_in_form('customclaimname'), ['size' => 40]); + $mform->setType('customclaimname', PARAM_TEXT); + $mform->disabledIf('customclaimname', 'bindingusernameclaim', 'neq', 'custom'); // Enable only if "Custom" is selected. + + // Custom claim name description. + $mform->addElement('static', 'customclaimname_description', '', get_string('customclaimname_description', 'auth_oidc')); + } + + // Save buttons. + $this->add_action_buttons(); + } +} diff --git a/auth/oidc/classes/form/change_binding_username_claim_tool_form1.php b/auth/oidc/classes/form/change_binding_username_claim_tool_form1.php new file mode 100644 index 00000000000..4e9172d8070 --- /dev/null +++ b/auth/oidc/classes/form/change_binding_username_claim_tool_form1.php @@ -0,0 +1,73 @@ +. + +/** + * Change binding username claim tool form 1. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2023 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\form; + +use core_text; +use csv_import_reader; +use html_writer; +use moodle_url; +use moodleform; + +/** + * Class change_binding_username_claim_tool_form1 represents the form on the change binding username claim tool page. + */ +class change_binding_username_claim_tool_form1 extends moodleform { + /** + * Form definition. + * + * @return void + */ + protected function definition() { + $mform =& $this->_form; + + $url = new moodle_url('/auth/oidc/example.csv'); + $link = html_writer::link($url, 'example.csv'); + $mform->addElement('static', 'example.csv', get_string('examplecsv', 'auth_oidc'), $link); + + $mform->addElement('filepicker', 'usernamefile', get_string('usernamefile', 'auth_oidc')); + $mform->addRule('usernamefile', null, 'required', null, 'client'); + + $choices = csv_import_reader::get_delimiter_list(); + $mform->addElement('select', 'delimiter_name', get_string('csvdelimiter', 'auth_oidc'), $choices); + if (array_key_exists('cfg', $choices)) { + $mform->setDefault('delimiter_name', 'cfg'); + } else if (get_string('listsep', 'langconfig') == ';') { + $mform->setDefault('delimiter_name', 'semicolon'); + } else { + $mform->setDefault('delimiter_name', 'comma'); + } + + $choices = core_text::get_encodings(); + $mform->addElement('select', 'encoding', get_string('encoding', 'auth_oidc'), $choices); + $mform->setDefault('encoding', 'UTF-8'); + + $choices = ['10' => 10, '20' => 20, '100' => 100, '1000' => 1000, '100000' => 100000]; + $mform->addElement('select', 'previewrowsl', get_string('rowpreviewnum', 'auth_oidc'), $choices); + $mform->setDefault('previewrowsl', 10); + + $this->add_action_buttons(false, get_string('upload_usernames', 'auth_oidc')); + } +} diff --git a/auth/oidc/classes/form/change_binding_username_claim_tool_form2.php b/auth/oidc/classes/form/change_binding_username_claim_tool_form2.php new file mode 100644 index 00000000000..75ee514580f --- /dev/null +++ b/auth/oidc/classes/form/change_binding_username_claim_tool_form2.php @@ -0,0 +1,53 @@ +. + +/** + * Change binding username claim tool form 2. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2023 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\form; + +use moodleform; + +/** + * Class change_binding_username_claim_tool_form2 represents the form on the change binding username claim tool page. + */ +class change_binding_username_claim_tool_form2 extends moodleform { + /** + * Form definition. + * + * @return void + */ + public function definition() { + $mform =& $this->_form; + $data = $this->_customdata['data']; + + $mform->addElement('hidden', 'iid'); + $mform->setType('iid', PARAM_INT); + + $mform->addElement('hidden', 'previewrows'); + $mform->setType('previewrows', PARAM_INT); + + $this->add_action_buttons(true, get_string('upload_usernames', 'auth_oidc')); + + $this->set_data($data); + } +} diff --git a/auth/oidc/classes/form/disconnect.php b/auth/oidc/classes/form/disconnect.php new file mode 100644 index 00000000000..8bddab4b300 --- /dev/null +++ b/auth/oidc/classes/form/disconnect.php @@ -0,0 +1,97 @@ +. + +/** + * OIDC disconnect form. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\form; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot.'/lib/formslib.php'); + +/** + * OIDC Disconnect Form. + */ +class disconnect extends \moodleform { + /** + * Form definition. + */ + protected function definition() { + global $USER, $DB; + + if (!empty($this->_customdata['userid'])) { + $userrec = $DB->get_record('user', ['id' => $this->_customdata['userid']]); + } else { + $userrec = $DB->get_record('user', ['id' => $USER->id]); + } + + $authconfig = get_config('auth_oidc'); + $opname = (!empty($authconfig->opname)) ? $authconfig->opname : get_string('pluginname', 'auth_oidc'); + + $mform =& $this->_form; + $mform->addElement('html', \html_writer::tag('h4', get_string('ucp_disconnect_title', 'auth_oidc', $opname))); + $mform->addElement('html', \html_writer::div(get_string('ucp_disconnect_details', 'auth_oidc', $opname))); + $mform->addElement('html', '
'); + $mform->addElement('hidden', 'redirect', $this->_customdata['redirect']); + $mform->setType('redirect', PARAM_URL); + $mform->addElement('hidden', 'donotremovetokens', $this->_customdata['donotremovetokens']); + $mform->setType('donotremovetokens', PARAM_BOOL); + + $mform->addElement('header', 'userdetails', get_string('userdetails')); + + $newmethod = []; + $attributes = []; + $manualenabled = (is_enabled_auth('manual') === true) ? true : false; + if ($manualenabled === true) { + $newmethod[] =& $mform->createElement('radio', 'newmethod', '', 'manual', 'manual', $attributes); + } + if (!empty($this->_customdata['prevmethod'])) { + $prevmethod = $this->_customdata['prevmethod']; + $newmethod[] =& $mform->createElement('radio', 'newmethod', '', $prevmethod, $prevmethod, $attributes); + } + $mform->addGroup($newmethod, 'newmethodar', get_string('errorauthdisconnectnewmethod', 'auth_oidc'), [' '], false); + if (!empty($this->_customdata['prevmethod'])) { + $mform->setDefault('newmethod', $this->_customdata['prevmethod']); + } else if ($manualenabled === true) { + $mform->setDefault('newmethod', 'manual'); + } + + if ($manualenabled === true) { + $mform->addElement('html', \html_writer::div(get_string('errorauthdisconnectifmanual', 'auth_oidc'))); + $mform->addElement('text', 'username', get_string('username')); + $mform->addElement('passwordunmask', 'password', get_string('password')); + $mform->setType('username', PARAM_USERNAME); + $mform->disabledIf('username', 'newmethod', 'neq', 'manual'); + $mform->disabledIf('password', 'newmethod', 'neq', 'manual'); + + // If the user cannot choose a username, set it to their current username and freeze. + if (isset($this->_customdata['canchooseusername']) && $this->_customdata['canchooseusername'] == false) { + $mform->setDefault('username', $userrec->username); + $element = $mform->getElement('username'); + $element->freeze(); + } + } + + $this->add_action_buttons(); + } +} diff --git a/auth/oidc/classes/httpclient.php b/auth/oidc/classes/httpclient.php new file mode 100644 index 00000000000..a8d5f2f7a37 --- /dev/null +++ b/auth/oidc/classes/httpclient.php @@ -0,0 +1,110 @@ +. + +/** + * HTTP clinet. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/lib/filelib.php'); + +/** + * Implementation of \auth_oidc\httpclientinterface using Moodle CURL. + */ +class httpclient extends \curl implements \auth_oidc\httpclientinterface { + /** + * Generate a client tag. + * + * @return string A client tag. + */ + protected function get_clienttag_headers() { + global $CFG; + + $iid = sha1($CFG->wwwroot); + $mdlver = $this->get_moodle_version(); + $ostype = php_uname('s'); + $osver = php_uname('r'); + $arch = php_uname('m'); + $ver = $this->get_plugin_version(); + + $params = "lang=PHP; os={$ostype}; os_version={$osver}; arch={$arch}; version={$ver}; MoodleInstallId={$iid}"; + $clienttag = "Moodle/{$mdlver} ({$params})"; + return [ + 'User-Agent: '.$clienttag, + 'X-ClientService-ClientTag: '.$clienttag, + ]; + } + + /** + * Get the current plugin version. + * + * @return string The current plugin version. + */ + protected function get_plugin_version() { + global $CFG; + $plugin = new \stdClass; + require_once($CFG->dirroot.'/auth/oidc/version.php'); + return (isset($plugin->release)) ? $plugin->release : 'unknown'; + } + + /** + * Get the current Moodle version. + * + * @return string The current Moodle version. + */ + protected function get_moodle_version() { + global $CFG; + return $CFG->release; + } + + /** + * Single HTTP Request + * + * @param string $url The URL to request + * @param array $options + * @return bool + */ + protected function request($url, $options = []) { + $this->setHeader($this->get_clienttag_headers()); + $result = parent::request($url, $options); + $this->resetHeader(); + return $result; + } + + /** + * HTTP POST method. + * + * @param string $url + * @param array|string $params + * @param array $options + * @return bool + */ + public function post($url, $params = '', $options = []) { + // Encode data to disable uploading files when values are prefixed @. + if (is_array($params)) { + $params = http_build_query($params, '', '&'); + } + return parent::post($url, $params, $options); + } +} diff --git a/auth/oidc/classes/httpclientinterface.php b/auth/oidc/classes/httpclientinterface.php new file mode 100644 index 00000000000..f4b5529d3a7 --- /dev/null +++ b/auth/oidc/classes/httpclientinterface.php @@ -0,0 +1,41 @@ +. + +/** + * HTTP client interface. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc; + +/** + * Interface defining an HTTP client. + */ +interface httpclientinterface { + /** + * HTTP POST method + * + * @param string $url + * @param array|string $params + * @param array $options + * @return bool + */ + public function post($url, $params = '', $options = []); +} diff --git a/auth/oidc/classes/jwt.php b/auth/oidc/classes/jwt.php new file mode 100644 index 00000000000..d66de9e592a --- /dev/null +++ b/auth/oidc/classes/jwt.php @@ -0,0 +1,155 @@ +. + +/** + * JWT token. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc; + +use moodle_exception; + +/** + * Class for working with JWTs. + */ +class jwt { + /** @var array Array of JWT header parameters. */ + protected $header = []; + + /** @var array Array of JWT claims. */ + protected $claims = []; + + /** + * Decode an encoded JWT. + * + * @param string $encoded Encoded JWT. + * @return array Array of arrays of header and body parameters. + * @throws moodle_exception + */ + public static function decode($encoded) { + if (empty($encoded) || !is_string($encoded)) { + throw new moodle_exception('errorjwtempty', 'auth_oidc'); + } + + // Separate JWT into parts. + $jwtparts = explode('.', $encoded); + if (count($jwtparts) !== 3) { + throw new moodle_exception('errorjwtmalformed', 'auth_oidc'); + } + + // Process header. + $header = base64_decode($jwtparts[0]); + if (!empty($header)) { + $header = @json_decode($header, true); + } + if (empty($header) || !is_array($header)) { + throw new moodle_exception('errorjwtcouldnotreadheader', 'auth_oidc'); + } + if (!isset($header['alg'])) { + throw new moodle_exception('errorjwtinvalidheader', 'auth_oidc'); + } + + // Process payload. + $jwsalgs = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'PS256', 'none']; + if (in_array($header['alg'], $jwsalgs, true) === true) { + $body = static::decode_jws($jwtparts[1]); + } else { + throw new moodle_exception('errorjwtunsupportedalg', 'auth_oidc'); + } + + if (empty($body) || !is_array($body)) { + throw new moodle_exception('errorjwtbadpayload', 'auth_oidc'); + } + + return [$header, $body]; + } + + /** + * Decode the payload of a JWS. + * + * @param string $jwtpayload JWT body part. + * @return array|null An array of payload claims, or null if there was a problem decoding. + */ + public static function decode_jws(string $jwtpayload) { + $body = strtr($jwtpayload, '-_', '+/'); + $body = base64_decode($body); + if (!empty($body)) { + $body = @json_decode($body, true); + } + return (!empty($body) && is_array($body)) ? $body : null; + } + + /** + * Create an instance of the class from an encoded JWT string. + * + * @param string $encoded The encoded JWT. + * @return jwt A JWT instance. + * @throws moodle_exception + */ + public static function instance_from_encoded($encoded) { + [$header, $body] = static::decode($encoded); + $jwt = new static; + $jwt->set_header($header); + $jwt->set_claims($body); + return $jwt; + } + + /** + * Set the JWT header. + * + * @param array $params The header params to set. Note, this will overwrite the existing header completely. + */ + public function set_header(array $params) { + $this->header = $params; + } + + /** + * Set claims in the object. + * + * @param array $params An array of claims to set. This will be appended to existing claims. Claims with the same keys will be + * overwritten. + */ + public function set_claims(array $params) { + $this->claims = array_merge($this->claims, $params); + } + + /** + * Get the value of a claim. + * + * @param string $claim The name of the claim to get. + * @return mixed The value of the claim. + */ + public function claim($claim) { + return (isset($this->claims[$claim])) ? $this->claims[$claim] : null; + } + + /** + * Calculate client assertion using the key provided. + * + * @param string $privatekey + * @return string + */ + public function assert_token($privatekey) { + $assertion = \Firebase\JWT\JWT::encode($this->claims, $privatekey, 'RS256', null, $this->header); + + return $assertion; + } +} diff --git a/auth/oidc/classes/loginflow/authcode.php b/auth/oidc/classes/loginflow/authcode.php new file mode 100644 index 00000000000..a5455603b95 --- /dev/null +++ b/auth/oidc/classes/loginflow/authcode.php @@ -0,0 +1,818 @@ +. + +/** + * Authorization Code login flow. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\loginflow; + +use auth_oidc\event\user_authed; +use auth_oidc\event\user_rename_attempt; +use auth_oidc\jwt; +use auth_oidc\utils; +use core\output\notification; +use core_text; +use core_user; +use moodle_exception; +use moodle_url; +use pix_icon; +use stdClass; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/auth/oidc/lib.php'); +require_once($CFG->dirroot . '/user/lib.php'); + +/** + * Login flow for the oauth2 authorization code grant. + */ +class authcode extends base { + /** + * Returns a list of potential IdPs that this authentication plugin supports. Used to provide links on the login page. + * + * @param string $wantsurl The relative url fragment the user wants to get to. + * @return array Array of IdPs. + */ + public function loginpage_idp_list($wantsurl) { + if (!auth_oidc_is_setup_complete()) { + return []; + } + + if (!empty($this->config->customicon)) { + $icon = new pix_icon('0/customicon', get_string('pluginname', 'auth_oidc'), 'auth_oidc'); + } else { + $icon = (!empty($this->config->icon)) ? $this->config->icon : 'auth_oidc:o365'; + $icon = explode(':', $icon); + if (isset($icon[1])) { + [$iconcomponent, $iconkey] = $icon; + } else { + $iconcomponent = 'auth_oidc'; + $iconkey = 'o365'; + } + $icon = new pix_icon($iconkey, get_string('pluginname', 'auth_oidc'), $iconcomponent); + } + + return [ + [ + 'url' => new moodle_url('/auth/oidc/', ['source' => 'loginpage']), + 'icon' => $icon, + 'name' => strip_tags(format_text($this->config->opname)), + ], + ]; + } + + /** + * Get an OIDC parameter. + * + * This is a modification to PARAM_ALPHANUMEXT to add a few additional characters from Base64-variants. + * + * @param string $name The name of the parameter. + * @param string $fallback The fallback value. + * @return string The parameter value, or fallback. + */ + protected function getoidcparam($name, $fallback = '') { + $val = optional_param($name, $fallback, PARAM_RAW); + $val = trim($val); + $valclean = preg_replace('/[^A-Za-z0-9\_\-\.\+\/\=]/i', '', $val); + if ($valclean !== $val) { + utils::debug('Authorization error.', __METHOD__, $name); + throw new moodle_exception('errorauthgeneral', 'auth_oidc'); + } + return $valclean; + } + + /** + * Handle requests to the redirect URL. + * + * @return mixed Determined by loginflow. + */ + public function handleredirect() { + global $CFG, $SESSION; + + $error = optional_param('error', '', PARAM_TEXT); + $errordescription = optional_param('error_description', '', PARAM_TEXT); + $silentloginmode = get_config('auth_oidc', 'silentloginmode'); + $selectaccount = false; + if ($silentloginmode) { + if ($error == 'login_required') { + // If silent login mode is enabled and the error is 'login_required', redirect to the login page. + $loginpageurl = new moodle_url('/login/index.php', ['noredirect' => 1]); + redirect($loginpageurl); + die(); + } else if ($error == 'interaction_required') { + if (strpos($errordescription, 'multiple user identities') !== false) { + $selectaccount = true; + } else { + $loginpageurl = new moodle_url('/login/index.php', ['noredirect' => 1]); + redirect($loginpageurl); + die(); + } + } + } + + if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) { + $adminconsent = optional_param('admin_consent', '', PARAM_TEXT); + if ($adminconsent) { + $state = $this->getoidcparam('state'); + if (!empty($state)) { + $requestparams = [ + 'state' => $state, + 'error_description' => optional_param('error_description', '', PARAM_TEXT), + ]; + $this->handlecertadminconsentresponse($requestparams); + } + } + } + + $state = $this->getoidcparam('state'); + $code = $this->getoidcparam('code'); + $promptlogin = (bool)optional_param('promptlogin', 0, PARAM_BOOL); + $promptaconsent = (bool)optional_param('promptaconsent', 0, PARAM_BOOL); + $justauth = (bool)optional_param('justauth', 0, PARAM_BOOL); + if (!empty($state) && $selectaccount === false) { + $requestparams = [ + 'state' => $state, + 'code' => $code, + 'error_description' => optional_param('error_description', '', PARAM_TEXT), + ]; + // Response from OP. + $this->handleauthresponse($requestparams); + } else { + if (isloggedin() && !isguestuser() && empty($justauth) && empty($promptaconsent)) { + if (isset($SESSION->wantsurl) && (strpos($SESSION->wantsurl, $CFG->wwwroot) === 0)) { + $urltogo = $SESSION->wantsurl; + unset($SESSION->wantsurl); + } else { + $urltogo = new moodle_url('/'); + } + redirect($urltogo); + die(); + } + // Initial login request. + $stateparams = ['forceflow' => 'authcode']; + $extraparams = []; + if ($promptaconsent === true) { + $extraparams = ['prompt' => 'admin_consent']; + } + if ($justauth === true) { + $stateparams['justauth'] = true; + } + $this->initiateauthrequest($promptlogin, $stateparams, $extraparams, $selectaccount); + } + } + + /** + * This is the primary method that is used by the authenticate_user_login() function in moodlelib.php. + * + * @param string $username The username (with system magic quotes) + * @param string $password The password (with system magic quotes) + * @return bool Authentication success or failure. + */ + public function user_login($username, $password = null) { + global $CFG, $DB; + + // Check user exists. + $userfilters = ['username' => $username, 'mnethostid' => $CFG->mnet_localhost_id, 'auth' => 'oidc']; + $userexists = $DB->record_exists('user', $userfilters); + + // Check token exists. + $tokenrec = $DB->get_record('auth_oidc_token', ['username' => $username]); + $code = optional_param('code', null, PARAM_RAW); + $tokenvalid = (!empty($tokenrec) && !empty($code) && $tokenrec->authcode === $code) ? true : false; + return ($userexists === true && $tokenvalid === true) ? true : false; + } + + /** + * Initiate an authorization request to the configured OP. + * + * @param bool $promptlogin Whether to prompt for login or use existing session. + * @param array $stateparams Parameters to store as state. + * @param array $extraparams Additional parameters to send with the OIDC request. + * @param bool $selectaccount Whether to prompt the user to select an account. + */ + public function initiateauthrequest($promptlogin = false, array $stateparams = [], array $extraparams = [], + bool $selectaccount = false) { + $client = $this->get_oidcclient(); + $client->authrequest($promptlogin, $stateparams, $extraparams, $selectaccount); + } + + /** + * Initiaite an admin consent request when using Microsoft Identity Platform. + * + * @param array $stateparams + * @param array $extraparams + * @return void + */ + public function initiateadminconsentrequest(array $stateparams = [], array $extraparams = []) { + $client = $this->get_oidcclient(); + $client->adminconsentrequest($stateparams, $extraparams); + } + + /** + * Handles the response for certificate-based admin consent authorization. + * + * @param array $authparams Array of authorization parameters. + * @return void + * @throws moodle_exception + */ + protected function handlecertadminconsentresponse(array $authparams) { + global $CFG, $DB, $SESSION; + + if (!empty($authparams['error_description'])) { + utils::debug('Authorization error.', __METHOD__, $authparams); + redirect($CFG->wwwroot, get_string('errorauthgeneral', 'auth_oidc'), null, notification::NOTIFY_ERROR); + } + + if (!isset($authparams['state'])) { + utils::debug('No state received.', __METHOD__, $authparams); + throw new moodle_exception('errorauthunknownstate', 'auth_oidc'); + } + + // Validate and expire state. + $staterec = $DB->get_record('auth_oidc_state', ['state' => $authparams['state']]); + if (empty($staterec)) { + throw new moodle_exception('errorauthunknownstate', 'auth_oidc'); + } + + $orignonce = $staterec->nonce; + $additionaldata = []; + if (!empty($staterec->additionaldata)) { + $additionaldata = @unserialize($staterec->additionaldata); + if (!is_array($additionaldata)) { + $additionaldata = []; + } + } + $SESSION->stateadditionaldata = $additionaldata; + $DB->delete_records('auth_oidc_state', ['id' => $staterec->id]); + + // Get token. + $client = $this->get_oidcclient(); + $tokenparams = $client->app_access_token_request(); + if (!isset($tokenparams['access_token'])) { + throw new moodle_exception('errorauthnoaccesstoken', 'auth_oidc'); + } + + $eventdata = [ + 'other' => [ + 'authparams' => $authparams, + 'tokenparams' => $tokenparams, + 'statedata' => $additionaldata, + ], + ]; + $event = user_authed::create($eventdata); + $event->trigger(); + + $redirect = (!empty($additionaldata['redirect'])) ? $additionaldata['redirect'] : '/auth/oidc/ucp.php'; + redirect(new moodle_url($redirect)); + } + + /** + * Handle an authorization request response received from the configured OP. + * + * @param array $authparams Received parameters. + */ + protected function handleauthresponse(array $authparams) { + global $DB, $SESSION, $USER, $CFG; + + $sid = optional_param('session_state', '', PARAM_TEXT); + + if (!empty($authparams['error_description'])) { + utils::debug('Authorization error.', __METHOD__, $authparams); + redirect($CFG->wwwroot, get_string('errorauthgeneral', 'auth_oidc'), null, notification::NOTIFY_ERROR); + } + + if (!isset($authparams['code'])) { + utils::debug('No auth code received.', __METHOD__, $authparams); + throw new moodle_exception('errorauthnoauthcode', 'auth_oidc'); + } + + if (!isset($authparams['state'])) { + utils::debug('No state received.', __METHOD__, $authparams); + throw new moodle_exception('errorauthunknownstate', 'auth_oidc'); + } + + // Validate and expire state. + $staterec = $DB->get_record('auth_oidc_state', ['state' => $authparams['state']]); + if (empty($staterec)) { + throw new moodle_exception('errorauthunknownstate', 'auth_oidc'); + } + + $orignonce = $staterec->nonce; + $additionaldata = []; + if (!empty($staterec->additionaldata)) { + $additionaldata = @unserialize($staterec->additionaldata); + if (!is_array($additionaldata)) { + $additionaldata = []; + } + } + $SESSION->stateadditionaldata = $additionaldata; + $DB->delete_records('auth_oidc_state', ['id' => $staterec->id]); + + // Get token from auth code. + $client = $this->get_oidcclient(); + $tokenparams = $client->tokenrequest($authparams['code']); + if (!isset($tokenparams['id_token'])) { + throw new moodle_exception('errorauthnoidtoken', 'auth_oidc'); + } + + // Decode and verify ID token. + [$oidcuniqid, $idtoken] = $this->process_idtoken($tokenparams['id_token'], $orignonce); + + // Check restrictions. + $passed = $this->checkrestrictions($idtoken); + if ($passed !== true && empty($additionaldata['ignorerestrictions'])) { + $errstr = 'User prevented from logging in due to restrictions.'; + utils::debug($errstr, __METHOD__, $idtoken); + throw new moodle_exception('errorrestricted', 'auth_oidc'); + } + + // This is for setting the system API user. + if (isset($additionaldata['justauth']) && $additionaldata['justauth'] === true) { + $eventdata = [ + 'other' => [ + 'authparams' => $authparams, + 'tokenparams' => $tokenparams, + 'statedata' => $additionaldata, + ], + ]; + $event = user_authed::create($eventdata); + $event->trigger(); + return true; + } + + // Check if OIDC user is already migrated. + $tokenrec = $DB->get_record('auth_oidc_token', ['oidcuniqid' => $oidcuniqid]); + if (isloggedin() && !isguestuser() && (empty($tokenrec) || (isset($USER->auth) && $USER->auth !== 'oidc'))) { + // If user is already logged in and trying to link Microsoft 365 account or use it for OIDC. + // Check if that Microsoft 365 account already exists in moodle. + $oidcusername = $this->get_oidc_username_from_token_claim($idtoken); + + $userrec = $DB->count_records_sql('SELECT COUNT(*) + FROM {user} + WHERE username = ? + AND id != ?', + [$oidcusername, $USER->id]); + + if (!empty($userrec)) { + if (empty($additionaldata['redirect'])) { + $redirect = '/auth/oidc/ucp.php?o365accountconnected=true'; + } else if ($additionaldata['redirect'] == '/local/o365/ucp.php') { + $redirect = $additionaldata['redirect'].'?action=connection&o365accountconnected=true'; + } else { + throw new moodle_exception('errorinvalidredirect_message', 'auth_oidc'); + } + redirect(new moodle_url($redirect)); + } + + // If the user is already logged in we can treat this as a "migration" - a user switching to OIDC. + $connectiononly = false; + if (isset($additionaldata['connectiononly']) && $additionaldata['connectiononly'] === true) { + $connectiononly = true; + } + $this->handlemigration($oidcuniqid, $authparams, $tokenparams, $idtoken, $connectiononly); + $redirect = (!empty($additionaldata['redirect'])) ? $additionaldata['redirect'] : '/auth/oidc/ucp.php'; + redirect(new moodle_url($redirect)); + } else { + // Otherwise it's a user logging in normally with OIDC. + $this->handlelogin($oidcuniqid, $authparams, $tokenparams, $idtoken); + if ($USER->id && $DB->record_exists('auth_oidc_token', ['userid' => $USER->id])) { + $authoidsidrecord = new stdClass(); + $authoidsidrecord->userid = $USER->id; + $authoidsidrecord->sid = $sid; + $authoidsidrecord->timecreated = time(); + $DB->insert_record('auth_oidc_sid', $authoidsidrecord); + } + redirect(core_login_get_return_url()); + } + } + + /** + * Handle a user migration event. + * + * @param string $oidcuniqid A unique identifier for the user. + * @param array $authparams Parameters received from the auth request. + * @param array $tokenparams Parameters received from the token request. + * @param jwt $idtoken A JWT object representing the received id_token. + * @param bool $connectiononly Whether to just connect the user (true), or to connect and change login method (false). + */ + protected function handlemigration($oidcuniqid, $authparams, $tokenparams, $idtoken, $connectiononly = false) { + global $USER, $DB, $CFG; + + // Check if OIDC user is already connected to a Moodle user. + $tokenrec = $DB->get_record('auth_oidc_token', ['oidcuniqid' => $oidcuniqid]); + if (!empty($tokenrec)) { + $existinguserparams = ['username' => $tokenrec->username, 'mnethostid' => $CFG->mnet_localhost_id]; + $existinguser = $DB->get_record('user', $existinguserparams); + if (empty($existinguser)) { + $DB->delete_records('auth_oidc_token', ['id' => $tokenrec->id]); + } else { + if ($USER->username === $tokenrec->username) { + // Already connected to current user. + if ($connectiononly !== true && $USER->auth !== 'oidc') { + // Update auth plugin. + $DB->update_record('user', (object)['id' => $USER->id, 'auth' => 'oidc']); + $USER = $DB->get_record('user', ['id' => $USER->id]); + $USER->auth = 'oidc'; + } + $this->updatetoken($tokenrec->id, $authparams, $tokenparams); + return true; + } else { + // OIDC user connected to user that is not us. Can't continue. + throw new moodle_exception('errorauthuserconnectedtodifferent', 'auth_oidc'); + } + } + } + + // Check if Moodle user is already connected to an OIDC user. + $tokenrec = $DB->get_record('auth_oidc_token', ['userid' => $USER->id]); + if (!empty($tokenrec)) { + if ($tokenrec->oidcuniqid === $oidcuniqid) { + // Already connected to current user. + if ($connectiononly !== true && $USER->auth !== 'oidc') { + // Update auth plugin. + $DB->update_record('user', (object)['id' => $USER->id, 'auth' => 'oidc']); + $USER = $DB->get_record('user', ['id' => $USER->id]); + $USER->auth = 'oidc'; + } + $this->updatetoken($tokenrec->id, $authparams, $tokenparams); + return true; + } else { + throw new moodle_exception('errorauthuseralreadyconnected', 'auth_oidc'); + } + } + + // Create token data. + $tokenrec = $this->createtoken($oidcuniqid, $USER->username, $authparams, $tokenparams, $idtoken, $USER->id); + + $eventdata = [ + 'objectid' => $USER->id, + 'userid' => $USER->id, + 'other' => [ + 'username' => $USER->username, + 'userid' => $USER->id, + 'oidcuniqid' => $oidcuniqid, + ], + ]; + $event = \auth_oidc\event\user_connected::create($eventdata); + $event->trigger(); + + // Switch auth method, if requested. + if ($connectiononly !== true) { + if ($USER->auth !== 'oidc') { + $DB->delete_records('auth_oidc_prevlogin', ['userid' => $USER->id]); + $userrec = $DB->get_record('user', ['id' => $USER->id]); + if (!empty($userrec)) { + $prevloginrec = [ + 'userid' => $userrec->id, + 'method' => $userrec->auth, + 'password' => $userrec->password, + ]; + $DB->insert_record('auth_oidc_prevlogin', $prevloginrec); + } + } + $DB->update_record('user', (object)['id' => $USER->id, 'auth' => 'oidc']); + $USER = $DB->get_record('user', ['id' => $USER->id]); + $USER->auth = 'oidc'; + } + + return true; + } + + /** + * Determines whether the given Microsoft Entra ID UPN is already matched to a Moodle user (and has not been completed). + * + * @param string $entraidupn The Microsoft Entra ID UPN to check for a match. + * @return false|stdClass Either the matched Moodle user record, or false if not matched. + */ + protected function check_for_matched($entraidupn) { + global $DB; + + if (auth_oidc_is_local_365_installed()) { + $match = $DB->get_record('local_o365_connections', ['entraidupn' => $entraidupn]); + if (!empty($match) && \local_o365\utils::is_o365_connected($match->muserid) !== true) { + return $DB->get_record('user', ['id' => $match->muserid]); + } + } + + return false; + } + + /** + * Check for an existing user object. + * @param string $oidcuniqid The user object ID to look up. + * @param string $username The original username. + * @return string If there is an existing user object, return the username associated with it. + * If there is no existing user object, return the original username. + */ + protected function check_objects($oidcuniqid, $username) { + global $DB; + + $user = null; + if (auth_oidc_is_local_365_installed()) { + $sql = 'SELECT u.username + FROM {local_o365_objects} obj + JOIN {user} u ON u.id = obj.moodleid + WHERE obj.objectid = ? and obj.type = ?'; + $params = [$oidcuniqid, 'user']; + $user = $DB->get_record_sql($sql, $params); + } + + return (!empty($user)) ? $user->username : $username; + } + + /** + * Handle a login event. + * + * @param string $oidcuniqid A unique identifier for the user. + * @param array $authparams Parameters received from the auth request. + * @param array $tokenparams Parameters received from the token request. + * @param jwt $idtoken A JWT object representing the received id_token. + */ + protected function handlelogin(string $oidcuniqid, array $authparams, array $tokenparams, jwt $idtoken) { + global $DB, $CFG; + + $tokenrec = $DB->get_record('auth_oidc_token', ['oidcuniqid' => $oidcuniqid]); + + // Do not continue if auth plugin is not enabled. + if (!is_enabled_auth('oidc')) { + throw new moodle_exception('erroroidcnotenabled', 'auth_oidc', null, null, '1'); + } + + // Find the latest real Microsoft username. + $oidcusername = $this->get_oidc_username_from_token_claim($idtoken); + + $usernamechanged = false; + if ($oidcusername && $tokenrec && strtolower($oidcusername) !== strtolower($tokenrec->useridentifier)) { + $usernamechanged = true; + } + + $existingmatching = null; + if (auth_oidc_is_local_365_installed()) { + if ($existingmatching = $DB->get_record('local_o365_objects', ['type' => 'user', 'objectid' => $oidcuniqid])) { + $existinguser = core_user::get_user($existingmatching->moodleid); + if ($existinguser && strtolower($existingmatching->o365name) != strtolower($oidcusername) && + $existinguser->username != strtolower($oidcusername)) { + $usernamechanged = true; + } + } + } + + $supportuseridentifierchangeconfig = get_config('local_o365', 'support_user_identifier_change'); + + if (!empty($tokenrec)) { + // Already connected user. + if (empty($tokenrec->userid)) { + // Existing token record, but missing the user ID. + $user = null; + if ($usernamechanged) { + $user = $DB->get_record('user', ['username' => strtolower($oidcusername)]); + } + if (empty($user)) { + $user = $DB->get_record('user', ['username' => $tokenrec->username]); + } + + if (empty($user)) { + // Token exists, but it doesn't have a valid username. + // In this case, delete the token, and try to process login again. + $DB->delete_records('auth_oidc_token', ['id' => $tokenrec->id]); + return $this->handlelogin($oidcuniqid, $authparams, $tokenparams, $idtoken); + } + $tokenrec->userid = $user->id; + if ($usernamechanged) { + $tokenrec->useridentifier = strtolower($oidcusername); + } + $DB->update_record('auth_oidc_token', $tokenrec); + } else { + // Existing token with a user ID. + $user = $DB->get_record('user', ['id' => $tokenrec->userid]); + if (empty($user)) { + $failurereason = AUTH_LOGIN_NOUSER; + $eventdata = ['other' => ['username' => $tokenrec->username, 'reason' => $failurereason]]; + $event = \core\event\user_login_failed::create($eventdata); + $event->trigger(); + // Token is invalid, delete it. + $DB->delete_records('auth_oidc_token', ['id' => $tokenrec->id]); + return $this->handlelogin($oidcuniqid, $authparams, $tokenparams, $idtoken); + } + + // Handle username change - update token, update connection. + if ($usernamechanged) { + if ($supportuseridentifierchangeconfig != 1) { + // Username change is not supported, throw exception. + throw new moodle_exception('errorupnchangeisnotsupported', 'local_o365', null, null, '2'); + } + $potentialduplicateuser = core_user::get_user_by_username(strtolower($oidcusername)); + if ($potentialduplicateuser) { + // Username already exists, cannot change Moodle account username, throw exception. + throw new moodle_exception('erroruserwithusernamealreadyexists', 'auth_oidc', null, null, '2'); + } else { + // Username does not exist: + // 1. can change Moodle account username (if the user uses auth_oidc), + // 2. can change token record. + if ($user->auth == 'oidc') { + $user->username = strtolower($oidcusername); + user_update_user($user, false); + + $fullmessage = 'Attempt to change username of user ' . $user->id . ' from ' . + $tokenrec->useridentifier . ' to ' . strtolower($oidcusername); + $event = user_rename_attempt::create(['objectid' => $user->id, 'other' => $fullmessage, + 'userid' => $user->id]); + $event->trigger(); + + $tokenrec->username = strtolower($oidcusername); + } + + $tokenrec->useridentifier = $oidcusername; + $DB->update_record('auth_oidc_token', $tokenrec); + } + + // Update local_o365_objects table. + if (auth_oidc_is_local_365_installed()) { + if ($o365objectrecord = $DB->get_record('local_o365_objects', + ['moodleid' => $user->id, 'type' => 'user'])) { + $o365objectrecord->o365name = $oidcusername; + $DB->update_record('local_o365_objects', $o365objectrecord); + } + } + } + } + $username = $user->username; + $this->updatetoken($tokenrec->id, $authparams, $tokenparams); + $user = authenticate_user_login($username, '', true); + + if (!empty($user)) { + complete_user_login($user); + } else { + // There was a problem in authenticate_user_login. + throw new moodle_exception('errorauthgeneral', 'auth_oidc', null, null, '2'); + } + } else if ($usernamechanged) { + // User has connection record, but no token; and the user has been renamed in Microsoft. + // In this case, we need to: + // 1. attempt to update Moodle username, + // 2. create token record, + // 3. update connection record in local_o365_objects table. + + if ($supportuseridentifierchangeconfig != 1) { + throw new moodle_exception('errorupnchangeisnotsupported', 'local_o365', null, null, '2'); + } + + $existinguser = core_user::get_user($existingmatching->moodleid); + + $username = $this->get_oidc_username_from_token_claim($idtoken); + + $originalupn = null; + + if (empty($username)) { + $username = $oidcuniqid; + + // If upn claim is missing, it can mean either the IdP is not Microsoft Entra ID, or it's a guest user. + if (auth_oidc_is_local_365_installed()) { + $apiclient = \local_o365\utils::get_api(); + $userdetails = $apiclient->get_user($oidcuniqid); + if (!is_null($userdetails) && isset($userdetails['userPrincipalName']) && + stripos($userdetails['userPrincipalName'], '#EXT#') !== false && $idtoken->claim('unique_name')) { + $originalupn = $userdetails['userPrincipalName']; + $username = $idtoken->claim('unique_name'); + } + } + } + $username = trim(core_text::strtolower($username)); + + // Update username. + $userwithduplicateusername = core_user::get_user_by_username($username); + if ($userwithduplicateusername) { + // Cannot rename user, username already exists. + throw new moodle_exception('erroruserwithusernamealreadyexists', 'auth_oidc', null, null, '2'); + } else { + $originalusername = $existinguser->username; + $existinguser->username = $username; + user_update_user($existinguser, false); + + $fullmessage = + 'Attempt to change username of user ' . $existinguser->id . ' from ' . $originalusername . ' to ' . + $username; + $event = user_rename_attempt::create(['objectid' => $existinguser->id, 'other' => $fullmessage, + 'userid' => $existinguser->id]); + $event->trigger(); + } + + // Create token. + $this->createtoken($oidcuniqid, $username, $authparams, $tokenparams, $idtoken, 0, $originalupn); + + // Update connection record in local_o365_objects table. + $existingmatching->o365name = $oidcusername; + $DB->update_record('local_o365_objects', $existingmatching); + + $user = authenticate_user_login($username, '', true); + + if (!empty($user)) { + complete_user_login($user); + } else { + // There was a problem in authenticate_user_login. + throw new moodle_exception('errorauthgeneral', 'auth_oidc', null, null, '2'); + } + } else { + /* No existing token, user not connected. Possibilities: + - Matched user. + - New user (maybe create). + */ + + // Generate a Moodle username. + $username = $this->get_oidc_username_from_token_claim($idtoken); + + $originalupn = null; + + if (empty($username)) { + $username = $oidcuniqid; + + // If upn claim is missing, it can mean either the IdP is not Microsoft Entra ID, or it's a guest user. + if (auth_oidc_is_local_365_installed()) { + $apiclient = \local_o365\utils::get_api(); + $userdetails = $apiclient->get_user($oidcuniqid, true); + if (!is_null($userdetails) && isset($userdetails['userPrincipalName']) && + stripos($userdetails['userPrincipalName'], '#EXT#') !== false && $idtoken->claim('unique_name')) { + $originalupn = $userdetails['userPrincipalName']; + $username = $idtoken->claim('unique_name'); + } + } + } + + // See if we have an object listing. + $username = $this->check_objects($oidcuniqid, $username); + $matchedwith = $this->check_for_matched($username); + if (!empty($matchedwith)) { + if ($matchedwith->auth != 'oidc') { + $matchedwith->entraidupn = $username; + throw new moodle_exception('errorusermatched', 'auth_oidc', null, $matchedwith); + } + } + $username = trim(core_text::strtolower($username)); + $tokenrec = $this->createtoken($oidcuniqid, $username, $authparams, $tokenparams, $idtoken, 0, $originalupn); + + $existinguserparams = ['username' => $username, 'mnethostid' => $CFG->mnet_localhost_id]; + if ($DB->record_exists('user', $existinguserparams) !== true) { + // User does not exist. Create user if site allows, otherwise fail. + if (empty($CFG->authpreventaccountcreation)) { + if (!$CFG->allowaccountssameemail) { + $userinfo = $this->get_userinfo($username); + if ($DB->count_records('user', ['email' => $userinfo['email'], 'deleted' => 0]) > 0) { + throw new moodle_exception('errorauthloginfaileddupemail', 'auth_oidc', null, null, '1'); + } + } + $user = create_user_record($username, '', 'oidc'); + } else { + // Trigger login failed event. + $failurereason = AUTH_LOGIN_NOUSER; + $eventdata = ['other' => ['username' => $username, 'reason' => $failurereason]]; + $event = \core\event\user_login_failed::create($eventdata); + $event->trigger(); + throw new moodle_exception('errorauthloginfailednouser', 'auth_oidc', null, null, '1'); + } + } + + $user = authenticate_user_login($username, '', true); + + if (!empty($user)) { + $tokenrec = $DB->get_record('auth_oidc_token', ['id' => $tokenrec->id]); + // This should be already done in auth_plugin_oidc::user_authenticated_hook, but just in case... + if (!empty($tokenrec) && empty($tokenrec->userid)) { + $updatedtokenrec = new stdClass; + $updatedtokenrec->id = $tokenrec->id; + $updatedtokenrec->userid = $user->id; + $DB->update_record('auth_oidc_token', $updatedtokenrec); + } + complete_user_login($user); + } else { + // There was a problem in authenticate_user_login. Clean up incomplete token record. + if (!empty($tokenrec)) { + $DB->delete_records('auth_oidc_token', ['id' => $tokenrec->id]); + } + + redirect($CFG->wwwroot, get_string('errorauthgeneral', 'auth_oidc'), null, notification::NOTIFY_ERROR); + } + + } + return true; + } +} diff --git a/auth/oidc/classes/loginflow/base.php b/auth/oidc/classes/loginflow/base.php new file mode 100644 index 00000000000..219c6d4373c --- /dev/null +++ b/auth/oidc/classes/loginflow/base.php @@ -0,0 +1,780 @@ +. + +/** + * Definition of base login flow class. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\loginflow; + +use auth_oidc\jwt; +use auth_oidc\oidcclient; +use auth_oidc\utils; +use core_user; +use moodle_exception; +use stdClass; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/auth/oidc/lib.php'); + +/** + * A base loginflow class. + */ +class base { + /** @var object Plugin config. */ + public $config; + + /** @var \auth_oidc\httpclientinterface An HTTP client to use. */ + protected $httpclient; + + /** + * Constructor. + */ + public function __construct() { + $default = [ + 'opname' => get_string('pluginname', 'auth_oidc'), + ]; + $storedconfig = (array)get_config('auth_oidc'); + + foreach ($storedconfig as $configname => $configvalue) { + if (strpos($configname, 'field_updatelocal_') === 0 && $configvalue == 'always') { + $storedconfig[$configname] = 'onlogin'; + } + } + + $this->config = (object)array_merge($default, $storedconfig); + } + + /** + * Returns a list of potential IdPs that this authentication plugin supports. Used to provide links on the login page. + * + * @param string $wantsurl The relative url fragment the user wants to get to. + * @return array Array of idps. + */ + public function loginpage_idp_list($wantsurl) { + return []; + } + + /** + * This is the primary method that is used by the authenticate_user_login() function in moodlelib.php. + * + * @param string $username The username (with system magic quotes) + * @param string $password The password (with system magic quotes) + * @return bool Authentication success or failure. + */ + public function user_login($username, $password = null) { + return false; + } + + /** + * Provides a hook into the login page. + * + * @param object $frm The form object containing login page data. + * @param object $user The user object related to the login attempt. + * @return bool True if the hook was processed successfully. + */ + public function loginpage_hook(&$frm, &$user) { + return true; + } + + /** + * Read user information from external database and returns it as array(). + * + * @param string $username username + * @return mixed array with no magic quotes or false on error + */ + public function get_userinfo($username) { + global $DB; + + $tokenrec = $DB->get_record('auth_oidc_token', ['username' => $username]); + if (empty($tokenrec)) { + return false; + } + + $originaluser = new stdClass(); + if ($DB->record_exists('user', ['username' => $username])) { + $eventtype = 'login'; + $originaluser = core_user::get_user_by_username($username); + } else { + $eventtype = 'create'; + } + + $fieldmappingfromtoken = true; + + if (auth_oidc_is_local_365_installed()) { + // Check if multi tenants is enabled. User from additional tenants can only sync fields from token. + $userfromadditionaltenant = false; + $hostingtenantid = get_config('local_o365', 'entratenantid'); + $token = jwt::instance_from_encoded($tokenrec->token); + if ($token->claim('tid') != $hostingtenantid) { + $userfromadditionaltenant = true; + } + + if (!$userfromadditionaltenant) { + $userdatafetchedfromgraph = false; + if (\local_o365\feature\usersync\main::fieldmap_require_graph_api_call($eventtype)) { + // If local_o365 is installed, and connects to Microsoft Identity Platform (v2.0), + // or field mapping uses fields not covered by token, then call Graph API function to get user details. + $apiclient = \local_o365\utils::get_api(); + if ($apiclient) { + $fieldmappingfromtoken = false; + $userdata = $apiclient->get_user($tokenrec->oidcuniqid); + if ($userdata) { + $userdatafetchedfromgraph = true; + } + } + } + + if (!$userdatafetchedfromgraph) { + // If local_o365 is installed, but all field mapping fields are in token, then use token. + $fieldmappingfromtoken = false; + // Process both ID token and access tokens. + $tokenames = ['idtoken', 'token']; + + foreach ($tokenames as $tokename) { + $token = jwt::instance_from_encoded($tokenrec->$tokename); + + if (!isset($userdata['objectId'])) { + $objectid = $token->claim('oid'); + if (!empty($objectid)) { + $userdata['objectId'] = $objectid; + } + } + + if (!isset($userdata['userPrincipalName'])) { + if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) { + $upn = $token->claim('preferred_username'); + if (empty($upn)) { + $upn = $token->claim('email'); + } + } else { + $upn = $token->claim('upn'); + if (empty($upn)) { + $upn = $token->claim('unique_name'); + } + } + + if (!empty($upn)) { + $userdata['userPrincipalName'] = $upn; + } + } + + if (!isset($userdata['givenName'])) { + $firstname = $token->claim('given_name'); + if (!empty($firstname)) { + $userdata['givenName'] = $firstname; + } + } + + if (!isset($userdata['surname'])) { + $lastname = $token->claim('family_name'); + if (!empty($lastname)) { + $userdata['surname'] = $lastname; + } + } + + if (!isset($userdata['mail'])) { + $email = $token->claim('email'); + if (!empty($email)) { + $userdata['mail'] = $email; + } else { + if (!empty($upn)) { + $entraidemailvalidateresult = filter_var($upn, FILTER_VALIDATE_EMAIL); + if (!empty($entraidemailvalidateresult)) { + $userdata['mail'] = $entraidemailvalidateresult; + } + } + } + } + + if (!isset($userdata['bindingusernameclaim'])) { + $bindingusernameclaim = auth_oidc_get_binding_username_claim(); + if (!empty($bindingusernameclaim)) { + $userdata['bindingusernameclaim'] = $token->claim($bindingusernameclaim); + } + } + } + } + + // Call the function in local_o365 to map fields. + $updateduser = \local_o365\feature\usersync\main::apply_configured_fieldmap($userdata, $originaluser, $eventtype); + $userinfo = (array)$updateduser; + } + } + + if ($fieldmappingfromtoken) { + // If local_o365 is not installed, use information from user token. + $userdata = []; + + // Process both ID token and access tokens. + $tokenames = ['idtoken', 'token']; + + foreach ($tokenames as $tokename) { + try { + $token = jwt::instance_from_encoded($tokenrec->$tokename); + } catch (moodle_exception $e) { + // Error occurred when decoding a token, skip. + continue; + } + + if (!isset($userdata['objectId'])) { + // Use 'oid' if available (Microsoft-specific), or fall back to standard "sub" claim. + $objectid = $token->claim('oid'); + if (empty($objectid)) { + $objectid = $token->claim('sub'); + } + if (!empty($objectid)) { + $userdata['objectId'] = $objectid; + } + } + + if (!isset($userdata['userPrincipalName'])) { + if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) { + $upn = $token->claim('preferred_username'); + if (empty($upn)) { + $upn = $token->claim('email'); + } + } else { + $upn = $token->claim('upn'); + if (empty($upn)) { + $upn = $token->claim('unique_name'); + } + } + + if (!empty($upn)) { + $userdata['userPrincipalName'] = $upn; + } + } + + if (!isset($userdata['givenName'])) { + $firstname = $token->claim('given_name'); + if (!empty($firstname)) { + $userdata['givenName'] = $firstname; + } + } + + if (!isset($userdata['surname'])) { + $lastname = $token->claim('family_name'); + if (!empty($lastname)) { + $userdata['surname'] = $lastname; + } + } + + if (!isset($userdata['mail'])) { + $email = $token->claim('email'); + if (!empty($email)) { + $userdata['mail'] = $email; + } else { + if (!empty($upn)) { + $entraidemailvalidateresult = filter_var($upn, FILTER_VALIDATE_EMAIL); + if (!empty($entraidemailvalidateresult)) { + $userdata['mail'] = $entraidemailvalidateresult; + } + } + } + } + + if (!isset($userdata['bindingusernameclaim'])) { + $bindingusernameclaim = auth_oidc_get_binding_username_claim(); + if (!empty($bindingusernameclaim)) { + $userdata['bindingusernameclaim'] = $token->claim($bindingusernameclaim); + } + } + } + + $updateduser = static::apply_configured_fieldmap_from_token($userdata, $eventtype); + $userinfo = (array)$updateduser; + } + + return $userinfo; + } + + /** + * Apply configured field mapping from token information to an empty user object. + * + * @param array $userdata + * @param string $eventtype + * @return stdClass + */ + public static function apply_configured_fieldmap_from_token(array $userdata, string $eventtype) { + $user = new stdClass(); + + $fieldmappings = auth_oidc_get_field_mappings(); + + foreach ($fieldmappings as $localfield => $fieldmapping) { + $remotefield = $fieldmapping['field_map']; + $behavior = $fieldmapping['update_local']; + + if ($behavior !== 'on' . $eventtype && $behavior !== 'always') { + // Field mapping doesn't apply to this event type. + continue; + } + + if (isset($userdata[$remotefield])) { + $user->$localfield = $userdata[$remotefield]; + } + } + + return $user; + } + + /** + * Set an HTTP client to use. + * + * @param \auth_oidc\httpclientinterface $httpclient + */ + public function set_httpclient(\auth_oidc\httpclientinterface $httpclient) { + $this->httpclient = $httpclient; + } + + /** + * Handle OIDC disconnection from Moodle account. + * + * @param bool $justremovetokens If true, just remove the stored OIDC tokens for the user; otherwise, revert login methods. + * @param bool $donotremovetokens If true, do not remove tokens when disconnecting. This migrates from a login account + * to a "linked" account. + * @param \moodle_url|null $redirect URL to redirect to if successful. + * @param \moodle_url|null $selfurl The page this is accessed from, used for some redirects. + * @param int|null $userid ID of the user to disconnect; uses the current user if not provided. + */ + public function disconnect($justremovetokens = false, $donotremovetokens = false, ?\moodle_url $redirect = null, + ?\moodle_url $selfurl = null, $userid = null) { + global $USER, $DB, $CFG; + if ($redirect === null) { + $redirect = new \moodle_url('/auth/oidc/ucp.php'); + } + if ($selfurl === null) { + $selfurl = new \moodle_url('/auth/oidc/ucp.php', ['action' => 'disconnectlogin']); + } + + // Get the record of the user involved. Current user if no ID received. + if (empty($userid)) { + $userid = $USER->id; + } + $userrec = $DB->get_record('user', ['id' => $userid]); + if (empty($userrec)) { + redirect($redirect); + die(); + } + + if ($justremovetokens === true) { + // Delete token data. + $DB->delete_records('auth_oidc_token', ['userid' => $userrec->id]); + $eventdata = ['objectid' => $userrec->id, 'userid' => $userrec->id]; + $event = \auth_oidc\event\user_disconnected::create($eventdata); + $event->trigger(); + redirect($redirect); + } else { + global $OUTPUT, $PAGE; + require_once($CFG->dirroot.'/user/lib.php'); + $PAGE->set_url($selfurl->out()); + $PAGE->set_context(\context_system::instance()); + $PAGE->set_pagelayout('standard'); + $USER->editing = false; + + $ucptitle = get_string('ucp_disconnect_title', 'auth_oidc', $this->config->opname); + $PAGE->navbar->add($ucptitle, $PAGE->url); + $PAGE->set_title($ucptitle); + + // Check if we have recorded the user's previous login method. + $prevmethodrec = $DB->get_record('auth_oidc_prevlogin', ['userid' => $userrec->id]); + $prevauthmethod = null; + if (!empty($prevmethodrec) && is_enabled_auth($prevmethodrec->method) === true) { + $prevauthmethod = $prevmethodrec->method; + } + // Manual is always available, we don't need it twice. + if ($prevauthmethod === 'manual') { + $prevauthmethod = null; + } + + // We need either the user's previous method or the manual login plugin to be enabled for disconnection. + if (empty($prevauthmethod) && is_enabled_auth('manual') !== true) { + throw new moodle_exception('errornodisconnectionauthmethod', 'auth_oidc'); + } + + // Check to see if the user has a username created by OIDC, or a self-created username. + // OIDC-created usernames are usually very verbose, so we'll allow them to choose a sensible one. + // Otherwise, keep their existing username. + $oidctoken = $DB->get_record('auth_oidc_token', ['userid' => $userrec->id]); + $ccun = (isset($oidctoken->oidcuniqid) && strtolower($oidctoken->oidcuniqid) === $userrec->username) ? true : false; + $customdata = [ + 'canchooseusername' => $ccun, + 'prevmethod' => $prevauthmethod, + 'donotremovetokens' => $donotremovetokens, + 'redirect' => $redirect, + 'userid' => $userrec->id, + ]; + + $mform = new \auth_oidc\form\disconnect($selfurl, $customdata); + + if ($mform->is_cancelled()) { + redirect($redirect); + } else if ($fromform = $mform->get_data()) { + if (empty($fromform->newmethod) || ($fromform->newmethod !== $prevauthmethod && + $fromform->newmethod !== 'manual')) { + throw new moodle_exception('errorauthdisconnectinvalidmethod', 'auth_oidc'); + } + + $updateduser = new stdClass; + + if ($fromform->newmethod === 'manual') { + if (empty($fromform->password)) { + throw new moodle_exception('errorauthdisconnectemptypassword', 'auth_oidc'); + } + if ($customdata['canchooseusername'] === true) { + if (empty($fromform->username)) { + throw new moodle_exception('errorauthdisconnectemptyusername', 'auth_oidc'); + } + + if (strtolower($fromform->username) !== $userrec->username) { + $newusername = strtolower($fromform->username); + $usercheck = ['username' => $newusername, 'mnethostid' => $CFG->mnet_localhost_id]; + if ($DB->record_exists('user', $usercheck) === false) { + $updateduser->username = $newusername; + } else { + throw new moodle_exception('errorauthdisconnectusernameexists', 'auth_oidc'); + } + } + } + $updateduser->auth = 'manual'; + $updateduser->password = $fromform->password; + } else if ($fromform->newmethod === $prevauthmethod) { + $updateduser->auth = $prevauthmethod; + // We can't use user_update_user as it will rehash the value. + if (!empty($prevmethodrec->password)) { + $manualuserupdate = new stdClass; + $manualuserupdate->id = $userrec->id; + $manualuserupdate->password = $prevmethodrec->password; + $DB->update_record('user', $manualuserupdate); + } + } + + // Update user. + $updateduser->id = $userrec->id; + try { + user_update_user($updateduser); + } catch (moodle_exception $e) { + throw new moodle_exception($e->errorcode, '', $selfurl); + } + + // Delete token data. + if (empty($fromform->donotremovetokens)) { + $DB->delete_records('auth_oidc_token', ['userid' => $userrec->id]); + + $eventdata = ['objectid' => $userrec->id, 'userid' => $userrec->id]; + $event = \auth_oidc\event\user_disconnected::create($eventdata); + $event->trigger(); + } + + // If we're dealing with the current user, refresh the object. + if ($userrec->id == $USER->id) { + $USER = $DB->get_record('user', ['id' => $USER->id]); + } + + if (!empty($fromform->redirect)) { + redirect($fromform->redirect); + } else { + redirect($redirect); + } + } + + echo $OUTPUT->header(); + $mform->display(); + echo $OUTPUT->footer(); + } + } + + /** + * Handle requests to the redirect URL. + * + * @return mixed Determined by loginflow. + */ + public function handleredirect() { + + } + + /** + * Construct the OpenID Connect client. + * + * @return oidcclient The constructed client. + */ + protected function get_oidcclient() { + global $CFG; + if (empty($this->httpclient) || !($this->httpclient instanceof \auth_oidc\httpclientinterface)) { + $this->httpclient = new \auth_oidc\httpclient(); + } + + if (!auth_oidc_is_setup_complete()) { + throw new moodle_exception('errorauthnocredsandendpoints', 'auth_oidc'); + } + + $clientid = (isset($this->config->clientid)) ? $this->config->clientid : null; + $clientsecret = (isset($this->config->clientsecret)) ? $this->config->clientsecret : null; + $redirecturi = (!empty($CFG->loginhttps)) ? str_replace('http://', 'https://', $CFG->wwwroot) : $CFG->wwwroot; + $redirecturi .= '/auth/oidc/'; + $tokenresource = (isset($this->config->oidcresource)) ? $this->config->oidcresource : null; + $scope = (isset($this->config->oidcscope)) ? $this->config->oidcscope : null; + + $client = new oidcclient($this->httpclient); + $client->setcreds($clientid, $clientsecret, $redirecturi, $tokenresource, $scope); + + $client->setendpoints(['auth' => $this->config->authendpoint, 'token' => $this->config->tokenendpoint]); + + return $client; + } + + /** + * Process an idtoken, extract uniqid and construct jwt object. + * + * @param string $idtoken Encoded id token. + * @param string $orignonce Original nonce to validate received nonce against. + * @return array List of oidcuniqid and constructed idtoken jwt. + */ + protected function process_idtoken($idtoken, $orignonce = '') { + // Decode and verify idtoken. + $idtoken = jwt::instance_from_encoded($idtoken); + $sub = $idtoken->claim('sub'); + if (empty($sub)) { + utils::debug('Invalid idtoken', __METHOD__, $idtoken); + throw new moodle_exception('errorauthinvalididtoken', 'auth_oidc'); + } + $receivednonce = $idtoken->claim('nonce'); + if (!empty($orignonce) && (empty($receivednonce) || $receivednonce !== $orignonce)) { + utils::debug('Invalid nonce', __METHOD__, $idtoken); + throw new moodle_exception('errorauthinvalididtoken', 'auth_oidc'); + } + + // Use 'oid' if available (Microsoft-specific), or fall back to standard "sub" claim. + $oidcuniqid = $idtoken->claim('oid'); + if (empty($oidcuniqid)) { + $oidcuniqid = $idtoken->claim('sub'); + } + return [$oidcuniqid, $idtoken]; + } + + /** + * Check user restrictions, if present. + * + * This check will return false if there are restrictions in place that the user did not meet, otherwise it will return + * true. If there are no restrictions in place, this will return true. + * + * @param jwt $idtoken The ID token of the user who is trying to log in. + * @return bool Whether the restriction check passed. + */ + protected function checkrestrictions(jwt $idtoken) { + $restrictions = (isset($this->config->userrestrictions)) ? trim($this->config->userrestrictions) : ''; + $hasrestrictions = false; + $userpassed = false; + if ($restrictions !== '') { + $restrictions = explode("\n", $restrictions); + // Check main user identifier claim based on IdP type, and falls back to oidc-standard "sub" if still empty. + $oidcusername = $this->get_oidc_username_from_token_claim($idtoken); + foreach ($restrictions as $restriction) { + $restriction = trim($restriction); + if ($restriction !== '') { + $hasrestrictions = true; + ob_start(); + try { + $pattern = '/'.$restriction.'/'; + if (isset($this->config->userrestrictionscasesensitive) && !$this->config->userrestrictionscasesensitive) { + $pattern .= 'i'; + } + $count = @preg_match($pattern, $oidcusername, $matches); + if (!empty($count)) { + $userpassed = true; + break; + } + } catch (moodle_exception $e) { + $debugdata = [ + 'exception' => $e, + 'restriction' => $restriction, + 'tomatch' => $oidcusername, + ]; + utils::debug('Error running user restrictions.', __METHOD__, $debugdata); + } + $contents = ob_get_contents(); + ob_end_clean(); + if (!empty($contents)) { + $debugdata = [ + 'contents' => $contents, + 'restriction' => $restriction, + 'tomatch' => $oidcusername, + ]; + utils::debug('Output while running user restrictions.', __METHOD__, $debugdata); + } + } + } + } + return ($hasrestrictions === true && $userpassed !== true) ? false : true; + } + + /** + * Create a token for a user, thus linking a Moodle user to an OpenID Connect user. + * + * @param string $oidcuniqid A unique identifier for the user. + * @param array $username The username of the Moodle user to link to. + * @param array $authparams Parameters received from the auth request. + * @param array $tokenparams Parameters received from the token request. + * @param jwt $idtoken A JWT object representing the received id_token. + * @param int $userid + * @param null|string $originalupn + * @return stdClass The created token database record. + */ + protected function createtoken($oidcuniqid, $username, $authparams, $tokenparams, jwt $idtoken, $userid = 0, + $originalupn = null) { + global $DB; + + if (!is_null($originalupn)) { + $oidcusername = $originalupn; + } else { + // Determine remote username depending on IdP type, or fall back to standard 'sub'. + $oidcusername = $this->get_oidc_username_from_token_claim($idtoken, 'auto'); + } + + $useridentifier = $this->get_oidc_username_from_token_claim($idtoken); + + // We should not fail here (idtoken was verified earlier to at least contain 'sub', but just in case...). + if (empty($oidcusername) || empty($useridentifier)) { + throw new moodle_exception('errorauthinvalididtoken', 'auth_oidc'); + } + + // Cleanup old invalid token with the same oidcusername. + $DB->delete_records('auth_oidc_token', ['oidcusername' => $oidcusername]); + + // Handle "The existing token for this user does not contain a valid user ID" error. + if ($userid == 0) { + $userrec = $DB->get_record('user', ['username' => $username]); + if ($userrec) { + $userid = $userrec->id; + } + } + + $tokenrec = new stdClass; + $tokenrec->oidcuniqid = $oidcuniqid; + $tokenrec->username = $username; + $tokenrec->userid = $userid; + $tokenrec->oidcusername = $oidcusername; + $tokenrec->useridentifier = $useridentifier; + $tokenrec->tokenresource = !empty($tokenparams['resource']) ? $tokenparams['resource'] : $this->config->oidcresource; + $tokenrec->scope = !empty($tokenparams['scope']) ? $tokenparams['scope'] : $this->config->oidcscope; + $tokenrec->authcode = $authparams['code']; + $tokenrec->token = $tokenparams['access_token']; + if (!empty($tokenparams['expires_on'])) { + $tokenrec->expiry = $tokenparams['expires_on']; + } else if (isset($tokenparams['expires_in'])) { + $tokenrec->expiry = time() + $tokenparams['expires_in']; + } else { + $tokenrec->expiry = time() + DAYSECS; + } + $tokenrec->refreshtoken = !empty($tokenparams['refresh_token']) ? $tokenparams['refresh_token'] : ''; // TBD? + $tokenrec->idtoken = $tokenparams['id_token']; + $tokenrec->id = $DB->insert_record('auth_oidc_token', $tokenrec); + return $tokenrec; + } + + /** + * Update a token with a new auth code and access token data. + * + * @param int $tokenid The database record ID of the token to update. + * @param array $authparams Parameters received from the auth request. + * @param array $tokenparams Parameters received from the token request. + */ + protected function updatetoken($tokenid, $authparams, $tokenparams) { + global $DB; + $tokenrec = new stdClass; + $tokenrec->id = $tokenid; + $tokenrec->authcode = $authparams['code']; + $tokenrec->token = $tokenparams['access_token']; + if (!empty($tokenparams['expires_on'])) { + $tokenrec->expiry = $tokenparams['expires_on']; + } else if (isset($tokenparams['expires_in'])) { + $tokenrec->expiry = time() + $tokenparams['expires_in']; + } else { + $tokenrec->expiry = time() + DAYSECS; + } + $tokenrec->refreshtoken = !empty($tokenparams['refresh_token']) ? $tokenparams['refresh_token'] : ''; // TBD? + $tokenrec->idtoken = $tokenparams['id_token']; + $DB->update_record('auth_oidc_token', $tokenrec); + } + + /** + * Get OIDC username from token claims based on configured claim. + * + * @param jwt $idtoken The OIDC ID token. + * @param string $bindingusernameclaim The configured binding username claim. + * @return string|null The OIDC username if found, null otherwise. + */ + protected function get_oidc_username_from_token_claim(jwt $idtoken, string $bindingusernameclaim = ''): ?string { + if (empty($idtoken)) { + return ''; + } + + if (empty($bindingusernameclaim)) { + $bindingusernameclaim = get_config('auth_oidc', 'bindingusernameclaim'); + if (empty($bindingusernameclaim)) { + $bindingusernameclaim = 'auto'; + set_config('bindingusernameclaim', $bindingusernameclaim, 'auth_oidc'); + } + } + + switch ($bindingusernameclaim) { + case 'custom': + $bindingusernameclaim = get_config('auth_oidc', 'custombindingclaim'); + case 'preferred_username': + case 'email': + case 'upn': + case 'unique_name': + case 'sub': + case 'oid': + case 'samaccountname': + $oidcusername = $idtoken->claim($bindingusernameclaim); + break; + case 'auto': + if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) { + $oidcusername = $idtoken->claim('preferred_username'); + if (empty($oidcusername)) { + $oidcusername = $idtoken->claim('email'); + } + } else { + $oidcusername = $idtoken->claim('upn'); + if (empty($oidcusername)) { + $oidcusername = $idtoken->claim('unique_name'); + } + } + + if (empty($oidcusername)) { + $oidcusername = $idtoken->claim('oid'); // Azure-specific. + } + + if (empty($oidcusername)) { + $oidcusername = $idtoken->claim('sub'); + } + + break; + default: + $oidcusername = ''; + } + + return $oidcusername; + } +} diff --git a/auth/oidc/classes/loginflow/rocreds.php b/auth/oidc/classes/loginflow/rocreds.php new file mode 100644 index 00000000000..8eb1226bf5f --- /dev/null +++ b/auth/oidc/classes/loginflow/rocreds.php @@ -0,0 +1,193 @@ +. + +/** + * Resource Owner Password Credentials Grant login flow. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\loginflow; + +use auth_oidc\utils; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/auth/oidc/lib.php'); + +/** + * Login flow for the oauth2 resource owner credentials grant. + */ +class rocreds extends base { + /** + * Check for an existing user object. + * + * @param string $o356username + * + * @return string If there is an existing user object, return the username associated with it. + * If there is no existing user object, return the original username. + */ + protected function check_objects($o356username) { + global $DB; + + $user = null; + if (auth_oidc_is_local_365_installed()) { + $sql = 'SELECT u.username + FROM {local_o365_objects} obj + JOIN {user} u ON u.id = obj.moodleid + WHERE obj.o365name = ? and obj.type = ?'; + $params = [$o356username, 'user']; + $user = $DB->get_record_sql($sql, $params); + } + + return (!empty($user)) ? $user->username : $o356username; + } + + /** + * Provides a hook into the login page. + * + * @param stdClass $frm Form object. + * @param stdClass $user User object. + * @return bool + */ + public function loginpage_hook(&$frm, &$user) { + global $DB; + + if (empty($frm)) { + $frm = data_submitted(); + } + if (empty($frm)) { + return true; + } + + $username = $frm->username; + $password = $frm->password; + $auth = 'oidc'; + + $username = $this->check_objects($username); + if ($username !== $frm->username) { + $success = $this->user_login($username, $password); + if ($success === true) { + $existinguser = $DB->get_record('user', ['username' => $username]); + if (!empty($existinguser)) { + $user = $existinguser; + return true; + } + } + } + + $autoappend = get_config('auth_oidc', 'autoappend'); + if (empty($autoappend)) { + // If we're not doing autoappend, just let things flow naturally. + return true; + } + + $existinguser = $DB->get_record('user', ['username' => $username]); + if (!empty($existinguser)) { + // We don't want to prevent access to existing accounts. + return true; + } + + $username .= $autoappend; + $success = $this->user_login($username, $password); + if ($success !== true) { + // No o365 user, continue normally. + return false; + } + + $existinguser = $DB->get_record('user', ['username' => $username]); + if (!empty($existinguser)) { + $user = $existinguser; + return true; + } + + // The user is authenticated but user creation may be disabled. + if (!empty($CFG->authpreventaccountcreation)) { + $failurereason = AUTH_LOGIN_UNAUTHORISED; + + // Trigger login failed event. + $event = \core\event\user_login_failed::create(['other' => ['username' => $username, + 'reason' => $failurereason]]); + $event->trigger(); + + debugging('[client '.getremoteaddr()."] $CFG->wwwroot Unknown user, can not create new accounts: $username ". + $_SERVER['HTTP_USER_AGENT']); + + return false; + } + + $user = create_user_record($username, $password, $auth); + return true; + } + + /** + * This is the primary method that is used by the authenticate_user_login() function in moodlelib.php. + * + * @param string $username The username (with system magic quotes) + * @param string $password The password (with system magic quotes) + * @return bool Authentication success or failure. + */ + public function user_login($username, $password = null) { + global $DB; + + $client = $this->get_oidcclient(); + $authparams = ['code' => '']; + + $oidcusername = $username; + $oidctoken = $DB->get_records('auth_oidc_token', ['username' => $username]); + if (!empty($oidctoken)) { + $oidctoken = array_shift($oidctoken); + if (!empty($oidctoken) && !empty($oidctoken->oidcusername)) { + $oidcusername = $oidctoken->oidcusername; + } + } + + // Make request. + $tokenparams = $client->rocredsrequest($oidcusername, $password); + if (!empty($tokenparams) && isset($tokenparams['token_type']) && $tokenparams['token_type'] === 'Bearer') { + [$oidcuniqid, $idtoken] = $this->process_idtoken($tokenparams['id_token']); + + // Check restrictions. + $passed = $this->checkrestrictions($idtoken); + if ($passed !== true) { + $errstr = 'User prevented from logging in due to restrictions.'; + utils::debug($errstr, __METHOD__, $idtoken); + return false; + } + + $tokenrec = $DB->get_record('auth_oidc_token', ['oidcuniqid' => $oidcuniqid]); + if (!empty($tokenrec)) { + $this->updatetoken($tokenrec->id, $authparams, $tokenparams); + } else { + $originalupn = null; + if (auth_oidc_is_local_365_installed()) { + $apiclient = \local_o365\utils::get_api(); + $userdetails = $apiclient->get_user($oidcuniqid); + if (!is_null($userdetails) && isset($userdetails['userPrincipalName']) && + stripos($userdetails['userPrincipalName'], '#EXT#') !== false) { + $originalupn = $userdetails['userPrincipalName']; + } + } + $this->createtoken($oidcuniqid, $username, $authparams, $tokenparams, $idtoken, 0, $originalupn); + } + return true; + } + return false; + } +} diff --git a/auth/oidc/classes/observers.php b/auth/oidc/classes/observers.php new file mode 100644 index 00000000000..a51781fc29c --- /dev/null +++ b/auth/oidc/classes/observers.php @@ -0,0 +1,51 @@ +. + +/** + * Event observer handlers for auth_oidc plugin. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc; + +use core\event\user_deleted; +use core\event\user_loggedout; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot.'/lib/filelib.php'); + +/** + * Handles events. + */ +class observers { + /** + * Handle user_deleted event - clean up calendar subscriptions. + * + * @param user_deleted $event The triggered event. + * @return bool Success/Failure. + */ + public static function handle_user_deleted(user_deleted $event) { + global $DB; + $userid = $event->objectid; + $DB->delete_records('auth_oidc_token', ['userid' => $userid]); + return true; + } +} diff --git a/auth/oidc/classes/oidcclient.php b/auth/oidc/classes/oidcclient.php new file mode 100644 index 00000000000..54a57f5f022 --- /dev/null +++ b/auth/oidc/classes/oidcclient.php @@ -0,0 +1,435 @@ +. + +/** + * OIDC client. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc; + +use moodle_exception; +use moodle_url; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/auth/oidc/lib.php'); + +/** + * OpenID Connect Client + */ +class oidcclient { + /** @var httpclientinterface An HTTP client to use. */ + protected $httpclient; + + /** @var string The client ID. */ + protected $clientid; + + /** @var string The client secret. */ + protected $clientsecret; + + /** @var string The client redirect URI. */ + protected $redirecturi; + + /** @var array Array of endpoints. */ + protected $endpoints = []; + + /** @var string The resource of the token. */ + protected $tokenresource; + + /** @var string The scope of the token. */ + protected $scope; + + /** + * Constructor. + * + * @param httpclientinterface $httpclient An HTTP client to use for background communication. + */ + public function __construct(httpclientinterface $httpclient) { + $this->httpclient = $httpclient; + } + + /** + * Set client details/credentials. + * + * @param string $id The registered client ID. + * @param string $secret The registered client secret. + * @param string $redirecturi The registered client redirect URI. + * @param string $tokenresource The API URL + * @param string $scope The requested OID scope. + */ + public function setcreds($id, $secret, $redirecturi, $tokenresource = '', $scope = '') { + $this->clientid = $id; + $this->clientsecret = $secret; + $this->redirecturi = $redirecturi; + if (!empty($tokenresource)) { + $this->tokenresource = $tokenresource; + } else { + if (auth_oidc_is_local_365_installed()) { + if (\local_o365\rest\o365api::use_chinese_api() === true) { + $this->tokenresource = 'https://microsoftgraph.chinacloudapi.cn'; + } else { + $this->tokenresource = 'https://graph.microsoft.com'; + } + } else { + $this->tokenresource = 'https://graph.microsoft.com'; + } + + } + $this->scope = (!empty($scope)) ? $scope : 'openid profile email'; + } + + /** + * Get the set client ID. + * + * @return string The set client ID. + */ + public function get_clientid() { + return (isset($this->clientid)) ? $this->clientid : null; + } + + /** + * Get the set client secret. + * + * @return string The set client secret. + */ + public function get_clientsecret() { + return (isset($this->clientsecret)) ? $this->clientsecret : null; + } + + /** + * Get the set redirect URI. + * + * @return string The set redirect URI. + */ + public function get_redirecturi() { + return (isset($this->redirecturi)) ? $this->redirecturi : null; + } + + /** + * Get the set token resource. + * + * @return string The set token resource. + */ + public function get_tokenresource() { + return (isset($this->tokenresource)) ? $this->tokenresource : null; + } + + /** + * Get the set scope. + * + * @return string The set scope. + */ + public function get_scope() { + return (isset($this->scope)) ? $this->scope : null; + } + + /** + * Set OIDC endpoints. + * + * @param array $endpoints Array of endpoints. Can have keys 'auth', and 'token'. + */ + public function setendpoints($endpoints) { + foreach ($endpoints as $type => $uri) { + if (clean_param($uri, PARAM_URL) !== $uri) { + throw new moodle_exception('erroroidcclientinvalidendpoint', 'auth_oidc'); + } + $this->endpoints[$type] = $uri; + } + } + + /** + * Validate and return the specified endpoint. + * + * @param string $endpoint The endpoint key to retrieve. + * @return mixed|null The endpoint URL if available, otherwise null. + */ + public function get_endpoint($endpoint) { + return (isset($this->endpoints[$endpoint])) ? $this->endpoints[$endpoint] : null; + } + + /** + * Get an array of authorization request parameters. + * + * @param bool $promptlogin Whether to prompt for login or use existing session. + * @param array $stateparams Parameters to store as state. + * @param array $extraparams Additional parameters to send with the OIDC request. + * @param bool $selectaccount Whether to prompt the user to select an account. + * @return array Array of request parameters. + */ + protected function getauthrequestparams($promptlogin = false, array $stateparams = [], array $extraparams = [], + bool $selectaccount = false) { + global $SESSION; + + $nonce = 'N'.uniqid(); + + $params = [ + 'response_type' => 'code', + 'client_id' => $this->clientid, + 'scope' => $this->scope, + 'nonce' => $nonce, + 'response_mode' => 'form_post', + 'state' => $this->getnewstate($nonce, $stateparams), + 'redirect_uri' => $this->redirecturi, + ]; + + if (get_config('auth_oidc', 'idptype') != AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) { + $params['resource'] = $this->tokenresource; + } + + if ($promptlogin === true) { + $params['prompt'] = 'login'; + } else if ($selectaccount === true) { + $params['prompt'] = 'select_account'; + } else { + $silentloginmode = get_config('auth_oidc', 'silentloginmode'); + $source = optional_param('source', '', PARAM_RAW); + if ($silentloginmode && $source != 'loginpage') { + $params['prompt'] = 'none'; + } + } + + $domainhint = get_config('auth_oidc', 'domainhint'); + if (!empty($domainhint)) { + $params['domain_hint'] = $domainhint; + } + + $params = array_merge($params, $extraparams); + + return $params; + } + + /** + * Return params for an admin consent request. + * + * @param array $stateparams + * @param array $extraparams + * @return array + */ + protected function getadminconsentrequestparams(array $stateparams = [], array $extraparams = []) { + $nonce = 'N'.uniqid(); + + $params = [ + 'client_id' => $this->clientid, + 'scope' => 'https://graph.microsoft.com/.default', + 'state' => $this->getnewstate($nonce, $stateparams), + 'redirect_uri' => $this->redirecturi, + ]; + + $params = array_merge($params, $extraparams); + + return $params; + } + + /** + * Generate a new state parameter. + * + * @param string $nonce The generated nonce value. + * @param array $stateparams + * @return string The new state value. + */ + protected function getnewstate($nonce, array $stateparams = []) { + global $DB; + $staterec = new \stdClass; + $staterec->sesskey = sesskey(); + $staterec->state = random_string(15); + $staterec->nonce = $nonce; + $staterec->timecreated = time(); + $staterec->additionaldata = serialize($stateparams); + $DB->insert_record('auth_oidc_state', $staterec); + return $staterec->state; + } + + /** + * Perform an authorization request by redirecting resource owner's user agent to auth endpoint. + * + * @param bool $promptlogin Whether to prompt for login or use existing session. + * @param array $stateparams Parameters to store as state. + * @param array $extraparams Additional parameters to send with the OIDC request. + * @param bool $selectaccount Whether to prompt the user to select an account. + */ + public function authrequest($promptlogin = false, array $stateparams = [], array $extraparams = [], + bool $selectaccount = false) { + if (empty($this->clientid)) { + throw new moodle_exception('erroroidcclientnocreds', 'auth_oidc'); + } + + if (empty($this->endpoints['auth'])) { + throw new moodle_exception('erroroidcclientnoauthendpoint', 'auth_oidc'); + } + + $params = $this->getauthrequestparams($promptlogin, $stateparams, $extraparams, $selectaccount); + $redirecturl = new moodle_url($this->endpoints['auth'], $params); + redirect($redirecturl); + } + + /** + * Perform an admin consent request when using a Microsoft Identity Platform type IdP. + * + * @param array $stateparams + * @param array $extraparams + * @return void + */ + public function adminconsentrequest(array $stateparams = [], array $extraparams = []) { + $adminconsentendpoint = 'https://login.microsoftonline.com/organizations/v2.0/adminconsent'; + $params = $this->getadminconsentrequestparams($stateparams, $extraparams); + $redirecturl = new moodle_url($adminconsentendpoint, $params); + redirect($redirecturl); + } + + /** + * Make a token request using the resource-owner credentials login flow. + * + * @param string $username The resource owner's username. + * @param string $password The resource owner's password. + * @return array Received parameters. + */ + public function rocredsrequest($username, $password) { + if (empty($this->endpoints['token'])) { + throw new moodle_exception('erroroidcclientnotokenendpoint', 'auth_oidc'); + } + + if (strpos($this->endpoints['token'], 'https://') !== 0) { + throw new moodle_exception('erroroidcclientinsecuretokenendpoint', 'auth_oidc'); + } + + $params = [ + 'grant_type' => 'password', + 'username' => $username, + 'password' => $password, + 'scope' => 'openid profile email', + 'client_id' => $this->clientid, + 'client_secret' => $this->clientsecret, + ]; + + if (get_config('auth_oidc', 'idptype') != AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) { + $params['resource'] = $this->tokenresource; + } + + try { + $returned = $this->httpclient->post($this->endpoints['token'], $params); + return utils::process_json_response($returned, ['token_type' => null, 'id_token' => null]); + } catch (moodle_exception $e) { + utils::debug('Error in rocredsrequest request', __METHOD__, $e->getMessage()); + return false; + } + } + + /** + * Exchange an authorization code for an access token. + * + * @param string $code An authorization code. + * @return array Received parameters. + */ + public function tokenrequest($code) { + if (empty($this->endpoints['token'])) { + throw new moodle_exception('erroroidcclientnotokenendpoint', 'auth_oidc'); + } + + $params = [ + 'client_id' => $this->clientid, + 'grant_type' => 'authorization_code', + 'code' => $code, + 'redirect_uri' => $this->redirecturi, + ]; + + switch (get_config('auth_oidc', 'clientauthmethod')) { + case AUTH_OIDC_AUTH_METHOD_CERTIFICATE: + $params['client_assertion_type'] = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'; + $params['client_assertion'] = static::generate_client_assertion(); + $params['tenant'] = 'common'; + break; + default: + $params['client_secret'] = $this->clientsecret; + } + $returned = $this->httpclient->post($this->endpoints['token'], $params); + return utils::process_json_response($returned, ['id_token' => null]); + } + + /** + * Request an access token in Microsoft Identity Platform. + * + * @return array + */ + public function app_access_token_request() { + $params = [ + 'client_id' => $this->clientid, + 'scope' => 'https://graph.microsoft.com/.default', + 'grant_type' => 'client_credentials', + ]; + + switch (get_config('auth_oidc', 'clientauthmethod')) { + case AUTH_OIDC_AUTH_METHOD_CERTIFICATE: + $params['client_assertion_type'] = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'; + $params['client_assertion'] = static::generate_client_assertion(); + break; + default: + $params['client_secret'] = $this->clientsecret; + } + + $tokenendpoint = $this->endpoints['token']; + + $returned = $this->httpclient->post($tokenendpoint, $params); + return utils::process_json_response($returned, ['access_token' => null]); + } + + /** + * Calculate the return the assertion used in the token request in certificate connection method. + * + * @return string + * @throws moodle_exception + */ + public static function generate_client_assertion(): string { + $authoidcconfig = get_config('auth_oidc'); + $certsource = $authoidcconfig->clientcertsource; + + $clientcertpassphrase = null; + if (property_exists($authoidcconfig, 'clientcertpassphrase')) { + $clientcertpassphrase = $authoidcconfig->clientcertpassphrase; + } + + if ($certsource == AUTH_OIDC_AUTH_CERT_SOURCE_TEXT) { + $cert = openssl_x509_read($authoidcconfig->clientcert); + $privatekey = openssl_pkey_get_private($authoidcconfig->clientprivatekey, $clientcertpassphrase); + } else if ($certsource == AUTH_OIDC_AUTH_CERT_SOURCE_FILE) { + $cert = openssl_x509_read(utils::get_certpath()); + $privatekey = openssl_pkey_get_private(utils::get_keypath(), $clientcertpassphrase); + } else { + throw new moodle_exception('errorinvalidcertificatesource', 'auth_oidc'); + } + + $sh1hash = openssl_x509_fingerprint($cert); + $x5t = base64_encode(hex2bin($sh1hash)); + + $jwt = new jwt(); + $jwt->set_header(['alg' => 'RS256', 'typ' => 'JWT', 'x5t' => $x5t]); + $jwt->set_claims([ + 'aud' => $authoidcconfig->tokenendpoint, + 'exp' => strtotime('+10min'), + 'iss' => $authoidcconfig->clientid, + 'jti' => bin2hex(openssl_random_pseudo_bytes(16)), + 'nbf' => time(), + 'sub' => $authoidcconfig->clientid, + 'iat' => time(), + ]); + + return $jwt->assert_token($privatekey); + } +} diff --git a/auth/oidc/classes/preview.php b/auth/oidc/classes/preview.php new file mode 100644 index 00000000000..ba373e80da9 --- /dev/null +++ b/auth/oidc/classes/preview.php @@ -0,0 +1,143 @@ +. + +/** + * Update username feature preview table. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2023 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc; + +use core_text; +use core_user; +use csv_import_reader; +use html_table; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/csvlib.class.php'); + +/** + * Class preview represents the preview table. + */ +class preview extends html_table { + /** @var csv_import_reader */ + protected $cir; + /** @var array */ + protected $filecolumns; + /** @var int */ + protected $previewrows; + /** @var bool */ + protected $noerror = true; + + /** + * Preview constructor. + * + * @param csv_import_reader $cir + * @param array $filecolumns + * @param int $previewrows + */ + public function __construct(csv_import_reader $cir, array $filecolumns, int $previewrows) { + parent::__construct(); + + $this->cir = $cir; + $this->filecolumns = $filecolumns; + $this->previewrows = $previewrows; + + $this->id = 'username_update_preview'; + $this->attributes['class'] = 'generaltable'; + $this->tablealign = 'center'; + $this->header = []; + $this->data = $this->read_data(); + + $this->head[] = get_string('csvline', 'auth_oidc'); + foreach ($filecolumns as $column) { + $this->head[] = $column; + } + $this->head[] = get_string('status'); + } + + /** + * Read data. + * + * @return array + */ + protected function read_data(): array { + global $DB; + + $data = []; + $this->cir->init(); + $linenum = 1; + + while ($linenum <= $this->previewrows && $fields = $this->cir->next()) { + $hasfatalerror = false; + $linenum++; + $rowcols = []; + $rowcols['line'] = $linenum; + foreach ($fields as $key => $value) { + $rowcols[$this->filecolumns[$key]] = s(trim($value)); + } + $rowcols['status'] = []; + + if (!isset($rowcols['username']) || !isset($rowcols['new_username'])) { + $rowcols['status'][] = get_string('update_error_incomplete_line', 'auth_oidc'); + $hasfatalerror = true; + } + + $user = $DB->get_record('user', ['username' => $rowcols['username']]); + if (!$user) { + $user = $DB->get_record('user', ['email' => $rowcols['username']]); + if ($user) { + $rowcols['status'][] = get_string('update_warning_email_match', 'auth_oidc'); + } else { + $rowcols['status'][] = get_string('update_error_user_not_found', 'auth_oidc'); + } + } else if ($user->auth != 'oidc') { + $rowcols['status'][] = get_string('update_error_user_not_oidc', 'auth_oidc'); + } + + $lcnewusername = core_text::strtolower($rowcols['new_username']); + if ($lcnewusername != core_user::clean_field($lcnewusername, 'username')) { + $rowcols['status'][] = get_string('update_error_invalid_new_username', 'auth_oidc'); + $hasfatalerror = true; + } + + $this->noerror = !$hasfatalerror && $this->noerror; + $rowcols['status'] = join('
', $rowcols['status']); + $data[] = $rowcols; + } + + if ($fields = $this->cir->next()) { + $data[] = array_fill(0, count($fields) + 2, '...'); + } + $this->cir->close(); + + return $data; + } + + /** + * Get no error. + * + * @return bool + */ + public function get_no_error(): bool { + return $this->noerror; + } +} diff --git a/auth/oidc/classes/privacy/provider.php b/auth/oidc/classes/privacy/provider.php new file mode 100644 index 00000000000..4a6beb724ea --- /dev/null +++ b/auth/oidc/classes/privacy/provider.php @@ -0,0 +1,242 @@ +. + +/** + * Privacy subsystem implementation. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\privacy; + +defined('MOODLE_INTERNAL') || die(); + +use core_privacy\local\metadata\collection; +use core_privacy\local\request\contextlist; +use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\writer; + +/** + * Interface for handling user lists in the OIDC authentication plugin. + * + * @package auth_oidc + */ +interface auth_oidc_userlist extends \core_privacy\local\request\core_userlist_provider { +}; + +/** + * Privacy provider for auth_oidc. + */ +class provider implements + \core_privacy\local\request\plugin\provider, + \core_privacy\local\metadata\provider, + auth_oidc_userlist { + + /** + * Returns meta data about this system. + * + * @param collection $collection The initialised collection to add items to. + * @return collection A listing of user data stored through this system. + */ + public static function get_metadata(collection $collection): collection { + + $tables = [ + 'auth_oidc_prevlogin' => [ + 'userid', + 'method', + 'password', + ], + 'auth_oidc_token' => [ + 'oidcuniqid', + 'username', + 'userid', + 'oidcusername', + 'useridentifier', + 'scope', + 'tokenresource', + 'authcode', + 'token', + 'expiry', + 'refreshtoken', + 'idtoken', + ], + ]; + + foreach ($tables as $table => $fields) { + $fielddata = []; + foreach ($fields as $field) { + $fielddata[$field] = 'privacy:metadata:'.$table.':'.$field; + } + $collection->add_database_table( + $table, + $fielddata, + 'privacy:metadata:'.$table + ); + } + + return $collection; + } + + /** + * Get the list of contexts that contain user information for the specified user. + * + * @param int $userid The user to search. + * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. + */ + public static function get_contexts_for_userid(int $userid): contextlist { + $contextlist = new \core_privacy\local\request\contextlist(); + + $sql = "SELECT ctx.id + FROM {auth_oidc_token} tk + JOIN {context} ctx ON ctx.instanceid = tk.userid AND ctx.contextlevel = :contextlevel + WHERE tk.userid = :userid"; + $params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER]; + $contextlist->add_from_sql($sql, $params); + + $sql = "SELECT ctx.id + FROM {auth_oidc_prevlogin} pv + JOIN {context} ctx ON ctx.instanceid = pv.userid AND ctx.contextlevel = :contextlevel + WHERE pv.userid = :userid"; + $params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER]; + $contextlist->add_from_sql($sql, $params); + + return $contextlist; + } + + /** + * Get the list of users who have data within a context. + * + * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. + */ + public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) { + $context = $userlist->get_context(); + + if (!$context instanceof \context_user) { + return; + } + + $params = [ + 'contextuser' => CONTEXT_USER, + 'contextid' => $context->id, + ]; + + $sql = "SELECT ctx.instanceid as userid + FROM {auth_oidc_prevlogin} pl + JOIN {context} ctx + ON ctx.instanceid = pl.userid + AND ctx.contextlevel = :contextuser + WHERE ctx.id = :contextid"; + $userlist->add_from_sql('userid', $sql, $params); + + $sql = "SELECT ctx.instanceid as userid + FROM {auth_oidc_token} tk + JOIN {context} ctx + ON ctx.instanceid = tk.userid + AND ctx.contextlevel = :contextuser + WHERE ctx.id = :contextid"; + $userlist->add_from_sql('userid', $sql, $params); + } + + /** + * Export all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + */ + public static function export_user_data(approved_contextlist $contextlist) { + global $DB; + $user = $contextlist->get_user(); + $context = \context_user::instance($contextlist->get_user()->id); + $tables = static::get_table_user_map($user); + foreach ($tables as $table => $filterparams) { + $records = $DB->get_recordset($table, $filterparams); + foreach ($records as $record) { + writer::with_context($context)->export_data([ + get_string('privacy:metadata:auth_oidc', 'auth_oidc'), + get_string('privacy:metadata:'.$table, 'auth_oidc'), + ], $record); + } + } + } + + /** + * Get a map of database tables that contain user data, and the filters to get records for a user. + * + * @param \stdClass $user The user to get the map for. + * @return array The table user map. + */ + protected static function get_table_user_map(\stdClass $user): array { + $tables = [ + 'auth_oidc_prevlogin' => ['userid' => $user->id], + 'auth_oidc_token' => ['userid' => $user->id], + ]; + return $tables; + } + + /** + * Delete all data for all users in the specified context. + * + * @param context $context The specific context to delete data for. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + if ($context->contextlevel == CONTEXT_USER) { + self::delete_user_data($context->instanceid); + } + } + + /** + * Delete all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. + */ + public static function delete_data_for_user(approved_contextlist $contextlist) { + if (empty($contextlist->count())) { + return; + } + foreach ($contextlist->get_contexts() as $context) { + if ($context->contextlevel == CONTEXT_USER) { + self::delete_user_data($context->instanceid); + } + } + } + + /** + * This does the deletion of user data given a userid. + * + * @param int $userid The user ID + */ + private static function delete_user_data(int $userid) { + global $DB; + $DB->delete_records('auth_oidc_prevlogin', ['userid' => $userid]); + $DB->delete_records('auth_oidc_token', ['userid' => $userid]); + } + + /** + * Delete multiple users within a single context. + * + * @param \core_privacy\local\request\approved_userlist $userlist The approved context and user information to delete + * information for. + */ + public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) { + $context = $userlist->get_context(); + // Because we only use user contexts the instance ID is the user ID. + if ($context instanceof \context_user) { + self::delete_user_data($context->instanceid); + } + } +} diff --git a/auth/oidc/classes/process.php b/auth/oidc/classes/process.php new file mode 100644 index 00000000000..63349258c25 --- /dev/null +++ b/auth/oidc/classes/process.php @@ -0,0 +1,296 @@ +. + +/** + * Process binding username claim tool. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2023 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc; + +use core_text; +use core_user; +use csv_import_reader; +use moodle_exception; +use stdClass; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/auth/oidc/lib.php'); + +/** + * Class process represents the process binding username claim tool. + */ +class process { + /** + * Route for renaming in auth_oidc. + * + * @var int + */ + const ROUTE_AUTH_OIDC_RENAME = 1; + + /** + * Route for matching other authentication methods. + * + * @var int + */ + const ROUTE_AUTH_OTHER_MATCH = 2; + + /** @var csv_import_reader */ + protected $cir; + /** @var array */ + protected $filecolumns = null; + /** @var stdClass */ + protected $formdata; + /** @var update_progress_tracker */ + protected $upt; + /** @var int */ + protected $userserrors = 0; + /** @var int */ + protected $usersupdated = 0; + + /** + * Process constructor. + * + * @param csv_import_reader $cir + */ + public function __construct(csv_import_reader $cir) { + $this->cir = $cir; + } + + /** + * Get the file columns. + * + * @return array + * @throws moodle_exception + */ + public function get_file_columns(): array { + if ($this->filecolumns === null) { + $columns = $this->cir->get_columns(); + if (count($columns) != 2) { + $this->cir->close(); + $this->cir->cleanup(); + throw new moodle_exception('error_invalid_upload_file', 'auth_oidc'); + } + + $stdfields = ['username', 'new_username']; + $this->filecolumns = []; + + foreach ($columns as $key => $unused) { + $field = $columns[$key]; + $field = trim($field); + $lcfield = core_text::strtolower($field); + if (in_array($field, $stdfields) || in_array($lcfield, $stdfields)) { + $newfield = $lcfield; + } + if (in_array($newfield, $this->filecolumns)) { + $this->cir->close(); + $this->cir->cleanup(); + throw new moodle_exception('duplicate_upload_field', 'auth_oidc', '', $field); + } + $this->filecolumns[$key] = $newfield; + } + } + + return $this->filecolumns; + } + + /** + * Set the form data. + * + * @param stdClass $formdata + */ + public function set_form_data(stdClass $formdata) { + $this->formdata = $formdata; + } + + /** + * Process the CSV file. + */ + public function process() { + // Initialise the CSV import reader. + $this->cir->init(); + + $this->upt = new upload_process_tracker(); + $this->upt->start(); + + $linenum = 1; // Column header is first line. + while ($line = $this->cir->next()) { + $this->upt->flush(); + $linenum++; + + $this->upt->track('line', $linenum); + $this->process_line($line); + } + + $this->upt->close(); + $this->cir->close(); + $this->cir->cleanup(true); + } + + /** + * Process a line from the CSV file. + * + * @param array $line + */ + protected function process_line(array $line) { + global $DB; + + $username = ''; + $lcusername = ''; + $newusername = ''; + $lcnewusername = ''; + + foreach ($line as $keynum => $value) { + $key = $this->get_file_columns()[$keynum]; + if ($key == 'username') { + $username = $value; + $lcusername = core_text::strtolower($username); + $this->upt->track('username', $username); + } else if ($key == 'new_username') { + $newusername = $value; + $lcnewusername = core_text::strtolower($newusername); + } + } + + if (!$username || !$lcusername || !$newusername || !$lcnewusername) { + $this->upt->track('status', get_string('update_error_incomplete_line', 'auth_oidc')); + $this->userserrors++; + + return; + } + + $user = core_user::get_user_by_username($lcusername); + if (!$user) { + $user = core_user::get_user_by_email($lcusername); + $this->upt->track('status', get_string('update_warning_email_match', 'auth_oidc')); + } + + if ($user && $user->auth == 'oidc') { + $route = self::ROUTE_AUTH_OIDC_RENAME; + } else { + $route = self::ROUTE_AUTH_OTHER_MATCH; + } + + if ($newusername !== core_user::clean_field($newusername, 'username')) { + $this->upt->track('status', get_string('update_error_invalid_new_username', 'auth_oidc')); + $this->userserrors++; + + return; + } + + $this->upt->track('new_username', $newusername); + + // All check passed, update the user record. + $userupdated = false; + $authoidctokenupdated = false; + $localo365objectupdated = false; + + // Step 1: Update the user object, if route is auth_oidc rename. + if ($route == self::ROUTE_AUTH_OIDC_RENAME) { + $this->upt->track('id', $user->id); + + $user->username = $lcnewusername; + try { + user_update_user($user, false); + $userupdated = true; + } catch (moodle_exception $e) { + $this->upt->track('status', get_string('update_error_user_update_failed', 'auth_oidc')); + $this->userserrors++; + + return; + } + } + + // Step 2: Update the token record. + if ($route == self::ROUTE_AUTH_OIDC_RENAME) { + if ($tokenrecord = $DB->get_record('auth_oidc_token', ['userid' => $user->id])) { + $tokenrecord->username = $lcnewusername; + $tokenrecord->useridentifier = $newusername; + $DB->update_record('auth_oidc_token', $tokenrecord); + $authoidctokenupdated = true; + } + } else { + $sql = "SELECT * + FROM {auth_oidc_token} + WHERE lower(useridentifier) = ?"; + if ($tokenrecord = $DB->get_record_sql($sql, [$lcusername])) { + $tokenrecord->useridentifier = $newusername; + $DB->update_record('auth_oidc_token', $tokenrecord); + $authoidctokenupdated = true; + } + } + + // Step 3: Update connection record in local_o365_object table. + if (auth_oidc_is_local_365_installed()) { + if ($route == static::ROUTE_AUTH_OIDC_RENAME) { + if ($connectionrecord = $DB->get_record('local_o365_objects', ['type' => 'user', 'moodleid' => $user->id])) { + $connectionrecord->o365name = $newusername; + $DB->update_record('local_o365_objects', $connectionrecord); + $localo365objectupdated = true; + } + } else { + $sql = "SELECT * + FROM {local_o365_objects} + WHERE type = 'user' + AND lower(o365name) = ?"; + if ($connectionrecord = $DB->get_record_sql($sql, [$lcusername])) { + $connectionrecord->o365name = $newusername; + $DB->update_record('local_o365_objects', $connectionrecord); + $localo365objectupdated = true; + } + } + } + + if ($userupdated) { + $this->upt->track('status', get_string('update_success_username', 'auth_oidc')); + } + + if ($authoidctokenupdated) { + $this->upt->track('status', get_string('update_success_token', 'auth_oidc')); + } + + if ($localo365objectupdated) { + $this->upt->track('status', get_string('update_success_o365', 'auth_oidc')); + } + + if ($userupdated || $authoidctokenupdated || $localo365objectupdated) { + // At least one of the records has been updated. + $this->usersupdated++; + } else { + $this->upt->track('status', get_string('update_error_nothing_updated', 'auth_oidc')); + $this->userserrors++; + } + } + + /** + * Return stats about the process. + * + * @return array + */ + public function get_stats(): array { + $lines = []; + + $lines[] = get_string('update_stats_users_updated', 'auth_oidc', $this->usersupdated); + $lines[] = get_string('update_stats_users_errors', 'auth_oidc', $this->userserrors); + + return $lines; + } +} diff --git a/auth/oidc/classes/task/cleanup_oidc_sid.php b/auth/oidc/classes/task/cleanup_oidc_sid.php new file mode 100644 index 00000000000..b6ed866a165 --- /dev/null +++ b/auth/oidc/classes/task/cleanup_oidc_sid.php @@ -0,0 +1,49 @@ +. + +/** + * A scheduled task to clean up oidc sid records. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2021 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\task; + +use core\task\scheduled_task; + +/** + * A scheduled task that cleans up OIDC SID records. + */ +class cleanup_oidc_sid extends scheduled_task { + /** + * Get a descriptive name for the task. + */ + public function get_name() { + return get_string('task_cleanup_oidc_sid', 'auth_oidc'); + } + + /** + * Clean up OIDC SID records. + */ + public function execute() { + global $DB; + + $DB->delete_records_select('auth_oidc_sid', 'timecreated < ?', [strtotime('-1 day')]); + } +} diff --git a/auth/oidc/classes/task/cleanup_oidc_state_and_token.php b/auth/oidc/classes/task/cleanup_oidc_state_and_token.php new file mode 100644 index 00000000000..eb5304c2f40 --- /dev/null +++ b/auth/oidc/classes/task/cleanup_oidc_state_and_token.php @@ -0,0 +1,53 @@ +. + +/** + * A scheduled task to clean up oidc state and invalid token. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2021 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\task; + +use core\task\scheduled_task; + +/** + * A scheduled task that cleans up oidc states and tokens. + */ +class cleanup_oidc_state_and_token extends scheduled_task { + /** + * Get a descriptive name for the task. + */ + public function get_name() { + return get_string('task_cleanup_oidc_state_and_token', 'auth_oidc'); + } + + /** + * Clean up oidc state and invalid oidc token. + */ + public function execute() { + global $DB; + + // Clean up oidc state. + $DB->delete_records_select('auth_oidc_state', 'timecreated < ?', [strtotime('-5 min')]); + + // Clean up invalid oidc token. + $DB->delete_records('auth_oidc_token', ['userid' => 0]); + } +} diff --git a/auth/oidc/classes/tests/mockhttpclient.php b/auth/oidc/classes/tests/mockhttpclient.php new file mode 100644 index 00000000000..fd873b8c5da --- /dev/null +++ b/auth/oidc/classes/tests/mockhttpclient.php @@ -0,0 +1,83 @@ +. + +/** + * Mock http client used in unit test. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\tests; + +use moodle_exception; + +/** + * A mock HTTP client allowing set responses. + */ +class mockhttpclient extends \auth_oidc\httpclient { + /** @var string The stored set response. */ + protected $mockresponse = ''; + + /** @var int The index of the current response. */ + protected $curresponse = 0; + + /** + * Set a response to return. + * + * @param string $response The response to return. + */ + public function set_response($response) { + $this->set_responses([$response]); + } + + /** + * Set multiple responses. + * + * Responses will be returned in sequence every time $this->request is called. I.e. The first + * time request() is called, the first item in the response array will be returned, the second time it's + * called the second item will be returned, etc. + * + * @param array $responses Array of responses. + */ + public function set_responses(array $responses) { + $this->curresponse = 0; + $this->mockresponse = $responses; + } + + /** + * Return the set response instead of making the actual HTTP request. + * + * @param string $url The request URL + * @param array $options Additional curl options. + * @return string The set response. + */ + protected function request($url, $options = []) { + if (isset($this->mockresponse[$this->curresponse])) { + $response = $this->mockresponse[$this->curresponse]; + $this->curresponse++; + return $response; + } else { + $this->curresponse = 0; + if (!isset($this->mockresponse[$this->curresponse])) { + throw new moodle_exception('error_no_response_available', 'auth_oidc'); + } + return $this->mockresponse[$this->curresponse]; + } + } +} diff --git a/auth/oidc/classes/tests/mockoidcclient.php b/auth/oidc/classes/tests/mockoidcclient.php new file mode 100644 index 00000000000..20980616247 --- /dev/null +++ b/auth/oidc/classes/tests/mockoidcclient.php @@ -0,0 +1,55 @@ +. + +/** + * Mock OIDC client used in unit test. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\tests; + +/** + * A mock oidcclient class providing access to all inaccessible properties/methods. + */ +class mockoidcclient extends \auth_oidc\oidcclient { + /** @var \auth_oidc\httpclientinterface An HTTP client to use. */ + public $httpclient; + + /** @var array Array of endpoints. */ + public $endpoints = []; + + /** + * Stub method to access protected parent method. + * + * @param bool $promptlogin Whether to prompt for login or use existing session. + * @param array $stateparams Parameters to store as state. + * @param array $extraparams Additional parameters to send with the OIDC request. + * @param bool $selectaccount Whether to prompt the user to select an account. + * @return array Array of request parameters. + */ + public function getauthrequestparams( + $promptlogin = false, + array $stateparams = [], + array $extraparams = [], + bool $selectaccount = false + ) { + return parent::getauthrequestparams($promptlogin, $stateparams); + } +} diff --git a/auth/oidc/classes/upload_process_tracker.php b/auth/oidc/classes/upload_process_tracker.php new file mode 100644 index 00000000000..7935c9be229 --- /dev/null +++ b/auth/oidc/classes/upload_process_tracker.php @@ -0,0 +1,149 @@ +. + +/** + * Process binding username claim tool upload process tracker. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2023 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc; + +use html_writer; + +/** + * Class to track the progress of processing username claims uploads. + */ +class upload_process_tracker { + /** @var array */ + protected $_row; + /** @var array */ + public $columns = []; + /** @var array */ + protected $headers = []; + + /** + * upload_process_tracker constructor. + */ + public function __construct() { + $this->headers = [ + 'status' => get_string('status'), + 'line' => get_string('csvline', 'auth_oidc'), + 'id' => 'ID', + 'username' => get_string('username'), + 'new_username' => get_string('new_username', 'auth_oidc'), + ]; + $this->columns = array_keys($this->headers); + } + + /** + * Start the tracker. + * + * @return void + */ + public function start() { + $ci = 0; + echo html_writer::start_tag('table', ['class' => 'generaltable boxaligncenter flexible-wrap', + 'summary' => get_string('update_username_results', 'auth_oidc')]); + echo html_writer::start_tag('tr', ['class' => 'heading r0']); + foreach ($this->headers as $header) { + echo html_writer::tag('th', $header, ['class' => 'header c' . $ci++, 'scope' => 'col']); + } + echo html_writer::end_tag('tr'); + $this->_row = null; + } + + /** + * Flush the tracker. + * + * @return void + */ + public function flush() { + if (empty($this->_row) || empty($this->_row['line']['normal'])) { + // Nothing to print - each line has to have at least number. + $this->_row = []; + foreach ($this->columns as $col) { + $this->_row[$col] = ['normal' => '', 'info' => '', 'warning' => '', 'error' => '']; + } + + return; + } + $ci = 0; + $ri = 1; + echo ''; + foreach ($this->_row as $key => $field) { + foreach ($field as $type => $content) { + if ($field[$type] !== '') { + $field[$type] = '' . $field[$type] . ''; + } else { + unset($field[$type]); + } + } + echo ''; + if (!empty($field)) { + echo implode('
', $field); + } else { + echo ' '; + } + echo ''; + } + echo ''; + foreach ($this->columns as $col) { + $this->_row[$col] = ['normal' => '', 'info' => '', 'warning' => '', 'error' => '']; + } + } + + /** + * Track a message. + * + * @param string $col + * @param string $msg + * @param string $level + * @param bool $merge + * @return void + */ + public function track($col, $msg, $level = 'normal', $merge = true) { + if (empty($this->_row)) { + $this->flush(); + } + if (!in_array($col, $this->columns)) { + debugging('Incorrect column:' . $col); + + return; + } + if ($merge) { + if ($this->_row[$col][$level] != '') { + $this->_row[$col][$level] .= '
'; + } + $this->_row[$col][$level] .= $msg; + } else { + $this->_row[$col][$level] = $msg; + } + } + + /** + * Close the tracker. + * + * @return void + */ + public function close() { + $this->flush(); + echo html_writer::end_tag('table'); + } +} diff --git a/auth/oidc/classes/utils.php b/auth/oidc/classes/utils.php new file mode 100644 index 00000000000..93dfcfc58de --- /dev/null +++ b/auth/oidc/classes/utils.php @@ -0,0 +1,247 @@ +. + +/** + * Utility functions. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc; + +use Exception; +use moodle_exception; +use auth_oidc\event\action_failed; +use moodle_url; + +/** + * General purpose utility class. + */ +class utils { + /** + * Process an OIDC JSON response. + * + * @param string $response The received JSON. + * @param array $expectedstructure + * @return array The parsed JSON. + * @throws moodle_exception + */ + public static function process_json_response($response, array $expectedstructure = []) { + $result = @json_decode($response, true); + if (empty($result) || !is_array($result)) { + self::debug('Bad response received', __METHOD__, $response); + throw new moodle_exception('erroroidccall', 'auth_oidc'); + } + + if (isset($result['error'])) { + $errmsg = 'Error response received.'; + self::debug($errmsg, __METHOD__, $result); + if (isset($result['error_description'])) { + $isadminconsent = optional_param('admin_consent', false, PARAM_BOOL); + if ($isadminconsent) { + if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM && + auth_oidc_is_local_365_installed() && + $result['error'] === 'invalid_grant' && + isset($result['error_codes']) && count($result['error_codes']) == 1 && + $result['error_codes'][0] == 53003) { + $localo365configurationpageurl = new moodle_url('/admin/settings.php', ['section' => 'local_o365']); + throw new moodle_exception('settings_adminconsent_error_53003', 'local_o365', + $localo365configurationpageurl, '', $result['error_description']); + } + } + throw new moodle_exception('erroroidccall_message', 'auth_oidc', '', $result['error_description']); + } else { + throw new moodle_exception('erroroidccall', 'auth_oidc'); + } + } + + foreach ($expectedstructure as $key => $val) { + if (!isset($result[$key])) { + $errmsg = 'Invalid structure received. No "'.$key.'"'; + self::debug($errmsg, __METHOD__, $result); + throw new moodle_exception('erroroidccall', 'auth_oidc'); + } + + if ($val !== null && $result[$key] !== $val) { + $strreceivedval = self::tostring($result[$key]); + $strval = self::tostring($val); + $errmsg = 'Invalid structure received. Invalid "'.$key.'". Received "'.$strreceivedval.'", expected "'.$strval.'"'; + self::debug($errmsg, __METHOD__, $result); + throw new moodle_exception('erroroidccall', 'auth_oidc'); + } + } + return $result; + } + + /** + * Convert any value into a debuggable string. + * + * @param mixed $val The variable to convert. + * @return string A string representation. + */ + public static function tostring($val) { + if (is_scalar($val)) { + if (is_bool($val)) { + return '(bool)'.(string)(int)$val; + } else { + return '('.gettype($val).')'.(string)$val; + } + } else if (is_null($val)) { + return '(null)'; + } else if ($val instanceof Exception) { + $valinfo = [ + 'file' => $val->getFile(), + 'line' => $val->getLine(), + 'message' => $val->getMessage(), + ]; + if ($val instanceof moodle_exception) { + $valinfo['debuginfo'] = $val->debuginfo; + $valinfo['errorcode'] = $val->errorcode; + $valinfo['module'] = $val->module; + } + return json_encode($valinfo, JSON_PRETTY_PRINT); + } else { + return json_encode($val, JSON_PRETTY_PRINT); + } + } + + /** + * Record a debug message. + * + * @param string $message The debug message to log. + * @param string $where + * @param null $debugdata + */ + public static function debug($message, $where = '', $debugdata = null) { + $debugmode = (bool)get_config('auth_oidc', 'debugmode'); + if ($debugmode === true) { + $debugbacktrace = debug_backtrace(); + $debugbacktracechecksum = md5(json_encode($debugbacktrace)); + + $otherdata = static::make_json_safe([ + 'other' => [ + 'message' => $message, + 'where' => $where, + 'debugdata' => $debugdata, + 'backtrace_checksum' => $debugbacktracechecksum, + ], + ]); + $event = action_failed::create($otherdata); + $event->trigger(); + + $debugbacktracedata = [ + 'checksum' => $debugbacktracechecksum, + 'backtrace' => $debugbacktrace, + ]; + + debugging(json_encode($debugbacktracedata), DEBUG_DEVELOPER); + } + } + + /** + * Make a JSON structure safe for logging. + * + * @param mixed $data The data to make safe. + * @return mixed The safe data. + */ + private static function make_json_safe($data) { + if (is_array($data)) { + foreach ($data as $key => $value) { + $data[$key] = static::make_json_safe($value); + } + } else if (is_object($data)) { + $data = (array)$data; + foreach ($data as $key => $value) { + $data[$key] = static::make_json_safe($value); + } + } else if (is_bool($data)) { + $data = (int)$data; + } else if (is_null($data)) { + $data = null; + } else if (!is_scalar($data)) { + $data = (string)$data; + } + return $data; + } + + /** + * Get the redirect URL that should be set in the identity provider + * + * @return string The redirect URL. + */ + public static function get_redirecturl() { + $redirecturl = new moodle_url('/auth/oidc/'); + return $redirecturl->out(false); + } + + /** + * Get the front channel logout URL that should be set in the identity provider. + * + * @return string The redirect URL. + */ + public static function get_frontchannellogouturl() { + $logouturl = new moodle_url('/auth/oidc/logout.php'); + return $logouturl->out(false); + } + + /** + * Get and check existence of OIDC client certificate path. + * + * @return string|bool cert path if exists otherwise false + */ + public static function get_certpath() { + $clientcertfile = get_config('auth_oidc', 'clientcertfile'); + $certlocation = self::get_openssl_internal_path(); + $certfile = "$certlocation/$clientcertfile"; + + if (is_file($certfile) && is_readable($certfile)) { + return "file://$certfile"; + } + + return false; + } + + /** + * Get and check existence of OIDC client key path. + * + * @return string|bool key path if exists otherwise false + */ + public static function get_keypath() { + $clientprivatekeyfile = get_config('auth_oidc', 'clientprivatekeyfile'); + $keylocation = self::get_openssl_internal_path(); + $keyfile = "$keylocation/$clientprivatekeyfile"; + + if (is_file($keyfile) && is_readable($keyfile)) { + return "file://$keyfile"; + } + + return false; + } + + /** + * Get openssl cert base path, which is dataroot/microsoft_certs. + * + * @return string base path to put cert files + */ + public static function get_openssl_internal_path() { + global $CFG; + + return $CFG->dataroot . '/microsoft_certs'; + } +} diff --git a/auth/oidc/cleanupoidctokens.php b/auth/oidc/cleanupoidctokens.php new file mode 100644 index 00000000000..3d1a2845377 --- /dev/null +++ b/auth/oidc/cleanupoidctokens.php @@ -0,0 +1,107 @@ +. + +/** + * Admin page to cleanup oidc tokens. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +require_once(__DIR__ . '/../../config.php'); +require_once($CFG->libdir . '/adminlib.php'); +require_once($CFG->dirroot . '/auth/oidc/lib.php'); + +require_login(); + +$context = context_system::instance(); +$pageurl = new moodle_url('/auth/oidc/cleanupoidctokens.php'); + +admin_externalpage_setup('auth_oidc_cleanup_oidc_tokens'); + +require_admin(); + +$PAGE->set_url($pageurl); +$PAGE->set_context($context); +$PAGE->set_pagelayout('admin'); +$PAGE->set_heading(get_string('cleanup_oidc_tokens', 'auth_oidc')); +$PAGE->set_title(get_string('cleanup_oidc_tokens', 'auth_oidc')); + +$emptyuseridtokens = auth_oidc_get_tokens_with_empty_ids(); +$mismatchedtokens = auth_oidc_get_tokens_with_mismatched_usernames(); + +$tokenstoclean = $emptyuseridtokens + $mismatchedtokens; + +uasort($tokenstoclean, function($a, $b) { + return strcmp($a->oidcusername, $b->oidcusername); +}); + +$deletetokenid = optional_param('id', 0, PARAM_INT); +if ($deletetokenid) { + if (array_key_exists($deletetokenid, $tokenstoclean)) { + auth_oidc_delete_token($deletetokenid); + + redirect($pageurl, get_string('token_deleted', 'auth_oidc')); + } +} + +if ($tokenstoclean) { + $table = new html_table(); + $table->head = [ + get_string('table_token_id', 'auth_oidc'), + get_string('table_oidc_username', 'auth_oidc'), + get_string('table_oidc_unique_identifier', 'auth_oidc'), + get_string('table_token_unique_id', 'auth_oidc'), + get_string('table_matching_status', 'auth_oidc'), + get_string('table_matching_details', 'auth_oidc'), + get_string('table_action', 'auth_oidc'), + ]; + $table->colclasses = [ + 'leftalign', + 'leftalign', + 'leftalign', + 'leftalign', + 'leftalign', + 'leftalign', + 'centeralign', + ]; + $table->attributes['class'] = 'admintable generaltable'; + $table->id = 'cleanupoidctokens'; + $table->data = []; + foreach ($tokenstoclean as $item) { + $table->data[] = [ + $item->id, + $item->oidcusername, + $item->useridentifier, + $item->oidcuniqueid, + $item->matchingstatus, + $item->details, + $item->action, + ]; + } +} + +echo $OUTPUT->header(); + +if ($tokenstoclean) { + echo html_writer::table($table); +} else { + echo html_writer::span(get_string('no_token_to_cleanup', 'auth_oidc')); +} + +echo $OUTPUT->footer(); diff --git a/auth/oidc/db/access.php b/auth/oidc/db/access.php new file mode 100644 index 00000000000..715315eba5f --- /dev/null +++ b/auth/oidc/db/access.php @@ -0,0 +1,49 @@ +. + +/** + * Plugin capabilities. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +$capabilities = [ + 'auth/oidc:manageconnection' => [ + 'riskbitmask' => RISK_CONFIG, + 'captype' => 'write', + 'contextlevel' => CONTEXT_USER, + 'archetypes' => [], + ], + 'auth/oidc:manageconnectionconnect' => [ + 'riskbitmask' => RISK_CONFIG, + 'captype' => 'write', + 'contextlevel' => CONTEXT_USER, + 'archetypes' => [], + ], + 'auth/oidc:manageconnectiondisconnect' => [ + 'riskbitmask' => RISK_CONFIG, + 'captype' => 'write', + 'contextlevel' => CONTEXT_USER, + 'archetypes' => [], + ], +]; diff --git a/auth/oidc/db/events.php b/auth/oidc/db/events.php new file mode 100644 index 00000000000..376c05a7fdf --- /dev/null +++ b/auth/oidc/db/events.php @@ -0,0 +1,35 @@ +. + +/** + * Event observers. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +defined('MOODLE_INTERNAL') || die(); + +$observers = [ + [ + 'eventname' => '\core\event\user_deleted', + 'callback' => '\auth_oidc\observers::handle_user_deleted', + 'priority' => 200, + 'internal' => false, + ], +]; diff --git a/auth/oidc/db/install.php b/auth/oidc/db/install.php new file mode 100644 index 00000000000..768eef90fb9 --- /dev/null +++ b/auth/oidc/db/install.php @@ -0,0 +1,35 @@ +. + +/** + * Plugin installation script. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +/** + * Installation script. + */ +function xmldb_auth_oidc_install() { + // Set the default value for the bindingusernameclaim setting. + $bindingusernameclaimconfig = get_config('auth_oidc', 'bindingusernameclaim'); + if (empty($bindingusernameclaimconfig)) { + set_config('bindingusernameclaim', 'auto', 'auth_oidc'); + } +} diff --git a/auth/oidc/db/install.xml b/auth/oidc/db/install.xml new file mode 100644 index 00000000000..93029ea8195 --- /dev/null +++ b/auth/oidc/db/install.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
diff --git a/auth/oidc/db/tasks.php b/auth/oidc/db/tasks.php new file mode 100644 index 00000000000..a254fceba90 --- /dev/null +++ b/auth/oidc/db/tasks.php @@ -0,0 +1,47 @@ +. + +/** + * Plugin tasks. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2021 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +defined('MOODLE_INTERNAL') || die(); + +$tasks = [ + [ + 'classname' => 'auth_oidc\task\cleanup_oidc_state_and_token', + 'blocking' => 0, + 'minute' => '*', + 'hour' => '*', + 'day' => '*', + 'dayofweek' => '*', + 'month' => '*', + ], + [ + 'classname' => 'auth_oidc\task\cleanup_oidc_sid', + 'blocking' => 0, + 'minute' => '51', + 'hour' => '*', + 'day' => '*', + 'dayofweek' => '*', + 'month' => '*', + ], +]; diff --git a/auth/oidc/db/upgrade.php b/auth/oidc/db/upgrade.php new file mode 100644 index 00000000000..346db562ffd --- /dev/null +++ b/auth/oidc/db/upgrade.php @@ -0,0 +1,559 @@ +. + +/** + * Plugin upgrade script. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/auth/oidc/lib.php'); + +/** + * Update plugin. + * + * @param int $oldversion the version we are upgrading from + * @return bool result + */ +function xmldb_auth_oidc_upgrade($oldversion) { + global $DB; + + $dbman = $DB->get_manager(); + + if ($oldversion < 2014111703) { + // Lengthen field. + $table = new xmldb_table('auth_oidc_token'); + $field = new xmldb_field('scope', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'username'); + $dbman->change_field_type($table, $field); + + upgrade_plugin_savepoint(true, 2014111703, 'auth', 'oidc'); + } + + if ($oldversion < 2015012702) { + $table = new xmldb_table('auth_oidc_state'); + $field = new xmldb_field('additionaldata', XMLDB_TYPE_TEXT, null, null, null, null, null, 'timecreated'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + upgrade_plugin_savepoint(true, 2015012702, 'auth', 'oidc'); + } + + if ($oldversion < 2015012703) { + $table = new xmldb_table('auth_oidc_token'); + $field = new xmldb_field('oidcusername', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'username'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + upgrade_plugin_savepoint(true, 2015012703, 'auth', 'oidc'); + } + + if ($oldversion < 2015012704) { + // Update OIDC users. + $sql = 'SELECT u.id as userid, + u.username as username, + tok.id as tokenid, + tok.oidcuniqid as oidcuniqid, + tok.idtoken as idtoken, + tok.oidcusername as oidcusername + FROM {auth_oidc_token} tok + JOIN {user} u ON u.username = tok.username + WHERE u.auth = ? AND deleted = ?'; + $params = ['oidc', 0]; + $userstoupdate = $DB->get_recordset_sql($sql, $params); + foreach ($userstoupdate as $user) { + if (empty($user->idtoken)) { + continue; + } + + try { + // Decode idtoken and determine oidc username. + $idtoken = \auth_oidc\jwt::instance_from_encoded($user->idtoken); + $oidcusername = $idtoken->claim('upn'); + if (empty($oidcusername)) { + $oidcusername = $idtoken->claim('sub'); + } + + // Populate token oidcusername. + if (empty($user->oidcusername)) { + $updatedtoken = new stdClass; + $updatedtoken->id = $user->tokenid; + $updatedtoken->oidcusername = $oidcusername; + $DB->update_record('auth_oidc_token', $updatedtoken); + } + + // Update user username (if applicable), so user can use rocreds loginflow. + if ($user->username == strtolower($user->oidcuniqid)) { + // Old username, update to upn/sub. + if ($oidcusername != $user->username) { + // Update username. + $updateduser = new stdClass; + $updateduser->id = $user->userid; + $updateduser->username = $oidcusername; + $DB->update_record('user', $updateduser); + + $updatedtoken = new stdClass; + $updatedtoken->id = $user->tokenid; + $updatedtoken->username = $oidcusername; + $DB->update_record('auth_oidc_token', $updatedtoken); + } + } + } catch (moodle_exception $e) { + continue; + } + } + upgrade_plugin_savepoint(true, 2015012704, 'auth', 'oidc'); + } + + if ($oldversion < 2015012707) { + if (!$dbman->table_exists('auth_oidc_prevlogin')) { + $dbman->install_one_table_from_xmldb_file(__DIR__.'/install.xml', 'auth_oidc_prevlogin'); + } + upgrade_plugin_savepoint(true, 2015012707, 'auth', 'oidc'); + } + + if ($oldversion < 2015012710) { + // Lengthen field. + $table = new xmldb_table('auth_oidc_token'); + $field = new xmldb_field('scope', XMLDB_TYPE_TEXT, null, null, null, null, null, 'oidcusername'); + $dbman->change_field_type($table, $field); + upgrade_plugin_savepoint(true, 2015012710, 'auth', 'oidc'); + } + + if ($oldversion < 2015111904.01) { + // Ensure the username field in auth_oidc_token is lowercase. + $authtokensrs = $DB->get_recordset('auth_oidc_token'); + foreach ($authtokensrs as $authtokenrec) { + $newusername = trim(\core_text::strtolower($authtokenrec->username)); + if ($newusername !== $authtokenrec->username) { + $updatedrec = new stdClass; + $updatedrec->id = $authtokenrec->id; + $updatedrec->username = $newusername; + $DB->update_record('auth_oidc_token', $updatedrec); + } + } + upgrade_plugin_savepoint(true, 2015111904.01, 'auth', 'oidc'); + } + + if ($oldversion < 2015111905.01) { + // Update old endpoints. + $config = get_config('auth_oidc'); + if ($config->authendpoint === 'https://login.windows.net/common/oauth2/authorize') { + add_to_config_log('authendpoint', $config->authendpoint, 'https://login.microsoftonline.com/common/oauth2/authorize', + 'auth_oidc'); + set_config('authendpoint', 'https://login.microsoftonline.com/common/oauth2/authorize', 'auth_oidc'); + } + + if ($config->tokenendpoint === 'https://login.windows.net/common/oauth2/token') { + add_to_config_log('tokenendpoint', $config->tokenendpoint, 'https://login.microsoftonline.com/common/oauth2/token', + 'auth_oidc'); + set_config('tokenendpoint', 'https://login.microsoftonline.com/common/oauth2/token', 'auth_oidc'); + } + + upgrade_plugin_savepoint(true, 2015111905.01, 'auth', 'oidc'); + } + + if ($oldversion < 2018051700.01) { + $table = new xmldb_table('auth_oidc_token'); + $field = new xmldb_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'username'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + $sql = 'SELECT tok.id, tok.username, u.username, u.id as userid + FROM {auth_oidc_token} tok + JOIN {user} u ON u.username = tok.username'; + $records = $DB->get_recordset_sql($sql); + foreach ($records as $record) { + $newrec = new stdClass; + $newrec->id = $record->id; + $newrec->userid = $record->userid; + $DB->update_record('auth_oidc_token', $newrec); + } + } + upgrade_plugin_savepoint(true, 2018051700.01, 'auth', 'oidc'); + } + + if ($oldversion < 2020020301) { + $oldgraphtokens = $DB->get_records('auth_oidc_token', ['resource' => 'https://graph.windows.net']); + foreach ($oldgraphtokens as $graphtoken) { + $graphtoken->resource = 'https://graph.microsoft.com'; + $DB->update_record('auth_oidc_token', $graphtoken); + } + + $oidcresource = get_config('auth_oidc', 'oidcresource'); + if ($oidcresource !== false && strpos($oidcresource, 'windows') !== false) { + $existingoidcresource = get_config('auth_oidc', 'oidcresource'); + if ($existingoidcresource != 'https://graph.windows.net') { + add_to_config_log('oidcresource', $existingoidcresource, 'https://graph.microsoft.com', 'auth_oidc'); + } + set_config('oidcresource', 'https://graph.microsoft.com', 'auth_oidc'); + } + + upgrade_plugin_savepoint(true, 2020020301, 'auth', 'oidc'); + } + + if ($oldversion < 2020071503) { + $localo365singlesignoffsetting = get_config('local_o365', 'single_sign_off'); + if ($localo365singlesignoffsetting !== false) { + $existingsignlesignoffsetting = get_config('auth_oidc', 'single_sign_off'); + if ($existingsignlesignoffsetting !== true) { + add_to_config_log('single_sign_off', $existingsignlesignoffsetting, true, 'auth_oidc'); + } + set_config('single_sign_off', true, 'auth_oidc'); + unset_config('single_sign_off', 'local_o365'); + } + + upgrade_plugin_savepoint(true, 2020071503, 'auth', 'oidc'); + } + + if ($oldversion < 2020110901) { + if ($dbman->field_exists('auth_oidc_token', 'resource')) { + // Rename field resource on table auth_oidc_token to tokenresource. + $table = new xmldb_table('auth_oidc_token'); + + $field = new xmldb_field('resource', XMLDB_TYPE_CHAR, '127', null, XMLDB_NOTNULL, null, null, 'scope'); + + // Launch rename field resource. + $dbman->rename_field($table, $field, 'tokenresource'); + } + + // Oidc savepoint reached. + upgrade_plugin_savepoint(true, 2020110901, 'auth', 'oidc'); + } + + if ($oldversion < 2020110903) { + // Part 1: add index to auth_oidc_token table. + $table = new xmldb_table('auth_oidc_token'); + + // Define index userid (not unique) to be added to auth_oidc_token. + $useridindex = new xmldb_index('userid', XMLDB_INDEX_NOTUNIQUE, ['userid']); + + // Conditionally launch add index userid. + if (!$dbman->index_exists($table, $useridindex)) { + $dbman->add_index($table, $useridindex); + } + + // Define index username (not unique) to be added to auth_oidc_token. + $usernameindex = new xmldb_index('username', XMLDB_INDEX_NOTUNIQUE, ['username']); + + // Conditionally launch add index username. + if (!$dbman->index_exists($table, $usernameindex)) { + $dbman->add_index($table, $usernameindex); + } + + // Part 2: update Authorization and token end point URL. + $entratenant = get_config('local_o365', 'aadtenant'); + + if ($entratenant) { + $authorizationendpoint = get_config('auth_oidc', 'authendpoint'); + if ($authorizationendpoint == 'https://login.microsoftonline.com/common/oauth2/authorize') { + $authorizationendpoint = str_replace('common', $entratenant, $authorizationendpoint); + $existingauthorizationendpoint = get_config('auth_oidc', 'authendpoint'); + if ($existingauthorizationendpoint != $authorizationendpoint) { + add_to_config_log('authendpoint', $existingauthorizationendpoint, $authorizationendpoint, 'auth_oidc'); + } + set_config('authendpoint', $authorizationendpoint, 'auth_oidc'); + } + + $tokenendpoint = get_config('auth_oidc', 'tokenendpoint'); + if ($tokenendpoint == 'https://login.microsoftonline.com/common/oauth2/token') { + $tokenendpoint = str_replace('common', $entratenant, $tokenendpoint); + $existingtokenendpoint = get_config('auth_oidc', 'tokenendpoint'); + if ($existingtokenendpoint != $tokenendpoint) { + add_to_config_log('tokenendpoint', $existingtokenendpoint, $tokenendpoint, 'auth_oidc'); + } + set_config('tokenendpoint', $tokenendpoint, 'auth_oidc'); + } + } + + // Oidc savepoint reached. + upgrade_plugin_savepoint(true, 2020110903, 'auth', 'oidc'); + } + + if ($oldversion < 2021051701) { + // Migrate field mapping settings from local_o365. + $existingfieldmappingsettings = get_config('local_o365', 'fieldmap'); + if ($existingfieldmappingsettings !== false) { + $userfields = auth_oidc_get_all_user_fields(); + + $existingfieldmappingsettings = @unserialize($existingfieldmappingsettings); + if (is_array($existingfieldmappingsettings)) { + foreach ($existingfieldmappingsettings as $existingfieldmappingsetting) { + $fieldmap = explode('/', $existingfieldmappingsetting); + + if (count($fieldmap) !== 3) { + // Invalid settings, ignore. + continue; + } + + [$remotefield, $localfield, $behaviour] = $fieldmap; + + if ($remotefield == 'facsimileTelephoneNumber') { + $remotefield = 'faxNumber'; + } + + $existingmapsetting = get_config('auth_oidc', 'field_map_' . $localfield); + if ($existingmapsetting !== $remotefield) { + add_to_config_log('field_map_' . $localfield, $existingmapsetting, $remotefield, 'auth_oidc'); + } + set_config('field_map_' . $localfield, $remotefield, 'auth_oidc'); + + $existinglocksetting = get_config('auth_oidc', 'field_lock_' . $localfield); + if ($existinglocksetting !== 'unlocked') { + add_to_config_log('field_lock_' . $localfield, $existinglocksetting, 'unlocked', 'auth_oidc'); + } + set_config('field_lock_' . $localfield, 'unlocked', 'auth_oidc'); + + $existingupdatelocalsetting = get_config('auth_oidc', 'field_updatelocal_' . $localfield); + if ($existingupdatelocalsetting !== $behaviour) { + add_to_config_log('field_updatelocal_' . $localfield, $existingupdatelocalsetting, $behaviour, 'auth_oidc'); + } + set_config('field_updatelocal_' . $localfield, $behaviour, 'auth_oidc'); + + if (($key = array_search($localfield, $userfields)) !== false) { + unset($userfields[$key]); + } + } + + foreach ($userfields as $userfield) { + $existingmapsetting = get_config('auth_oidc', 'field_map_' . $userfield); + if ($existingmapsetting !== '') { + add_to_config_log('field_map_' . $userfield, $existingmapsetting, '', 'auth_oidc'); + } + set_config('field_map_' . $userfield, '', 'auth_oidc'); + + $existinglocksetting = get_config('auth_oidc', 'field_lock_' . $userfield); + if ($existinglocksetting !== 'unlocked') { + add_to_config_log('field_lock_' . $userfield, $existinglocksetting, 'unlocked', 'auth_oidc'); + } + set_config('field_lock_' . $userfield, 'unlocked', 'auth_oidc'); + + $existingupdatelocalsetting = get_config('auth_oidc', 'field_updatelocal_' . $userfield); + if ($existingupdatelocalsetting !== 'always') { + add_to_config_log('field_updatelocal_' . $userfield, $existingupdatelocalsetting, 'always', 'auth_oidc'); + } + set_config('field_updatelocal_' . $userfield, 'always', 'auth_oidc'); + } + } + + unset_config('fieldmap', 'local_o365'); + } + + // Oidc savepoint reached. + upgrade_plugin_savepoint(true, 2021051701, 'auth', 'oidc'); + } + + if ($oldversion < 2022041901) { + // Define field sid to be added to auth_oidc_token. + $table = new xmldb_table('auth_oidc_token'); + $field = new xmldb_field('sid', XMLDB_TYPE_CHAR, '36', null, null, null, null, 'idtoken'); + + // Conditionally launch add field sid. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Oidc savepoint reached. + upgrade_plugin_savepoint(true, 2022041901, 'auth', 'oidc'); + } + + if ($oldversion < 2022041906) { + // Update idptype config. + $idptypeconfig = get_config('auth_oidc', 'idptype'); + $authorizationendpoint = get_config('auth_oidc', 'authendpoint'); + if (empty($idptypeconfig)) { + if (!$authorizationendpoint) { + $existingidptype = get_config('auth_oidc', 'idptype'); + if ($existingidptype != AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID) { + add_to_config_log('idptype', $existingidptype, AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID, 'auth_oidc'); + } + set_config('idptype', AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID, 'auth_oidc'); + } else { + $endpointversion = auth_oidc_determine_endpoint_version($authorizationendpoint); + switch ($endpointversion) { + case AUTH_OIDC_MICROSOFT_ENDPOINT_VERSION_1: + $existingidptype = get_config('auth_oidc', 'idptype'); + if ($existinglocksetting != AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID) { + add_to_config_log('idptype', $existingidptype, AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID, 'auth_oidc'); + } + set_config('idptype', AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID, 'auth_oidc'); + break; + case AUTH_OIDC_MICROSOFT_ENDPOINT_VERSION_2: + $existingidptype = get_config('auth_oidc', 'idptype'); + if ($existinglocksetting != AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) { + add_to_config_log( + 'idptype', + $existingidptype, + AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM, + 'auth_oidc' + ); + } + set_config('idptype', AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM, 'auth_oidc'); + break; + default: + $existingidptype = get_config('auth_oidc', 'idptype'); + if ($existinglocksetting != AUTH_OIDC_IDP_TYPE_OTHER) { + add_to_config_log('idptype', $existingidptype, AUTH_OIDC_IDP_TYPE_OTHER, 'auth_oidc'); + } + set_config('idptype', AUTH_OIDC_IDP_TYPE_OTHER, 'auth_oidc'); + } + } + } + + // Update client authentication type configuration settings. + $clientauthmethodconfig = get_config('auth_oidc', 'clientauthmethod'); + if (empty($clientauthmethodconfig)) { + $clientsecretconfig = get_config('auth_oidc', 'clientsecret'); + $clientcertificateconfig = get_config('auth_oidc', 'clientcert'); + $clientprivatekeyconfig = get_config('auth_oidc', 'clientprivatekey'); + if (empty($clientsecretconfig) && !empty($clientcertificateconfig) && !empty($clientprivatekeyconfig)) { + $existingclientauthmethod = get_config('auth_oidc', 'clientauthmethod'); + if ($existingclientauthmethod != AUTH_OIDC_AUTH_METHOD_CERTIFICATE) { + add_to_config_log('clientauthmethod', $existingclientauthmethod, AUTH_OIDC_AUTH_METHOD_CERTIFICATE, + 'auth_oidc'); + } + set_config('clientauthmethod', AUTH_OIDC_AUTH_METHOD_CERTIFICATE, 'auth_oidc'); + } else { + $existingclientauthmethod = get_config('auth_oidc', 'clientauthmethod'); + if ($existingclientauthmethod != AUTH_OIDC_AUTH_METHOD_SECRET) { + add_to_config_log('clientauthmethod', $existingclientauthmethod, AUTH_OIDC_AUTH_METHOD_SECRET, 'auth_oidc'); + } + set_config('clientauthmethod', AUTH_OIDC_AUTH_METHOD_SECRET, 'auth_oidc'); + } + } + + // Update tenantnameorguid config. + $tenantnameorguidconfig = get_config('auth_oidc', 'tenantnameorguid'); + if (empty($tenantnameorguidconfig)) { + $entratenant = get_config('local_o365', 'aadtenant'); + if ($entratenant) { + $existingtenantnameorguid = get_config('auth_oidc', 'tenantnameorguid'); + if ($existingtenantnameorguid != $entratenant) { + add_to_config_log('tenantnameorguid', $existingtenantnameorguid, $entratenant, 'auth_oidc'); + } + set_config('tenantnameorguid', $entratenant, 'auth_oidc'); + } + } + + // Oidc savepoint reached. + upgrade_plugin_savepoint(true, 2022041906, 'auth', 'oidc'); + } + + if ($oldversion < 2022112801) { + // Update tenantnameorguid config. + unset_config('auth_oidc', 'tenantnameorguid'); + + // Oidc savepoint reached. + upgrade_plugin_savepoint(true, 2022112801, 'auth', 'oidc'); + } + + if ($oldversion < 2023100902) { + // Set initial value for "clientcertsource" config. + if (empty(get_config('auth_oidc', 'clientcertsource'))) { + $existingclientcertsource = get_config('auth_oidc', 'clientcertsource'); + if ($existingclientcertsource != AUTH_OIDC_AUTH_CERT_SOURCE_TEXT) { + add_to_config_log('clientcertsource', $existingclientcertsource, AUTH_OIDC_AUTH_CERT_SOURCE_TEXT, 'auth_oidc'); + } + set_config('clientcertsource', AUTH_OIDC_AUTH_CERT_SOURCE_TEXT, 'auth_oidc'); + } + + upgrade_plugin_savepoint(true, 2023100902, 'auth', 'oidc'); + } + + if ($oldversion < 2024042201) { + // Set default values for new settings "bindingusernameclaim" and "customclaimname". + if (!get_config('auth_oidc', 'bindingusernameclaim')) { + set_config('bindingusernameclaim', 'auto', 'auth_oidc'); + } + + if (!get_config('auth_oidc', 'customclaimname')) { + set_config('customclaimname', '', 'auth_oidc'); + } + + // Define field useridentifier to be added to auth_oidc_token. + $table = new xmldb_table('auth_oidc_token'); + $field = new xmldb_field('useridentifier', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'oidcusername'); + + // Conditionally launch add field useridentifier. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + + // Save current value of oidcusername to useridentifier. + $sql = 'UPDATE {auth_oidc_token} SET useridentifier = oidcusername'; + $DB->execute($sql); + } + + // Oidc savepoint reached. + upgrade_plugin_savepoint(true, 2024042201, 'auth', 'oidc'); + } + + if ($oldversion < 2024100701) { + // Set the default value for the bindingusernameclaim setting. + $bindingusernameclaimconfig = get_config('auth_oidc', 'bindingusernameclaim'); + if (empty($bindingusernameclaimconfig)) { + set_config('bindingusernameclaim', 'auto', 'auth_oidc'); + } + + // Oidc savepoint reached. + upgrade_plugin_savepoint(true, 2024100701, 'auth', 'oidc'); + } + + if ($oldversion < 2024100702) { + // Define table auth_oidc_sid to be created. + $table = new xmldb_table('auth_oidc_sid'); + + // Adding fields to table auth_oidc_sid. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '20', null, XMLDB_NOTNULL, null, null); + $table->add_field('sid', XMLDB_TYPE_CHAR, '36', null, XMLDB_NOTNULL, null, null); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + + // Adding keys to table auth_oidc_sid. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Conditionally launch create table for auth_oidc_sid. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Migrate existing sid values from auth_oidc_tokens to auth_oidc_sid. + if ($dbman->field_exists('auth_oidc_token', 'sid')) { + $sql = "INSERT INTO {auth_oidc_sid} (userid, sid, timecreated) + SELECT userid, sid, ? AS timecreated + FROM {auth_oidc_token} + WHERE sid IS NOT NULL AND sid != ''"; + $DB->execute($sql, [time()]); + } + + // Define field sid to be dropped from auth_oidc_token. + $table = new xmldb_table('auth_oidc_token'); + $field = new xmldb_field('sid'); + + // Conditionally launch drop field sid. + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + + // Oidc savepoint reached. + upgrade_plugin_savepoint(true, 2024100702, 'auth', 'oidc'); + } + + return true; +} diff --git a/auth/oidc/example.csv b/auth/oidc/example.csv new file mode 100644 index 00000000000..b2def12ab33 --- /dev/null +++ b/auth/oidc/example.csv @@ -0,0 +1,2 @@ +username,new_username +old_username@domain_A.com,725741c1-f2f2-4db4-9fcf-1a1363e58e4b \ No newline at end of file diff --git a/auth/oidc/index.php b/auth/oidc/index.php new file mode 100644 index 00000000000..1abea2e8136 --- /dev/null +++ b/auth/oidc/index.php @@ -0,0 +1,32 @@ +. + +/** + * Authentication landing page. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +// phpcs:ignore moodle.Files.RequireLogin.Missing +require_once(__DIR__.'/../../config.php'); +require_once(__DIR__.'/auth.php'); + +$auth = new \auth_plugin_oidc('authcode'); +$auth->set_httpclient(new \auth_oidc\httpclient()); +$auth->handleredirect(); diff --git a/auth/oidc/js/module.js b/auth/oidc/js/module.js new file mode 100644 index 00000000000..08c510f8adb --- /dev/null +++ b/auth/oidc/js/module.js @@ -0,0 +1,55 @@ +/*global $, M, sessionStorage*/ + +M.auth_oidc = {}; + +M.auth_oidc.init = function(Y, idptype_ms, authmethodsecret, authmethodcertificate, authmethodcertificatetext) { + var $idptype = $("#id_idptype"); + var $clientauthmethod = $("#id_clientauthmethod"); + var $clientsecret = $("#id_clientsecret"); + var $clientcert = $("#id_clientcert"); + var $clientprivatekey = $("#id_clientprivatekey"); + var $clientprivatekeyfile = $("#id_clientprivatekeyfile"); + var $clientcertfile = $("#id_clientcertfile"); + var $clientcertpassphrase = $("#id_clientcertpassphrase"); + var $clientcertsource = $("#id_clientcertsource"); + var $secretexpiryrecipients = $("#id_secretexpiryrecipients"); + + $idptype.change(function() { + if ($(this).val() != idptype_ms) { + $("#id_clientauthmethod option[value='" + authmethodcertificate + "']").each(function() { + $(this).remove(); + }); + $clientauthmethod.val(authmethodsecret); + $clientsecret.prop('disabled', false); + $clientcertsource.prop('disabled', true); + $clientcert.prop('disabled', true); + $clientprivatekey.prop('disabled', true); + $clientprivatekeyfile.prop('disabled', true); + $clientcertfile.prop('disabled', true); + $clientcertpassphrase.prop('disabled', true); + $secretexpiryrecipients.prop('disabled', false); + } else { + $clientauthmethod.append(""); + } + }); + + $clientauthmethod.change(function() { + if ($(this).val() == authmethodcertificate) { + if ($clientcertsource.val() == 'file') { + $clientcert.prop('disabled', true); + $clientprivatekey.prop('disabled', true); + $clientprivatekeyfile.prop('disabled', false); + $clientcertfile.prop('disabled', false); + } else { + $clientcert.prop('disabled', false); + $clientprivatekey.prop('disabled', false); + $clientprivatekeyfile.prop('disabled', true); + $clientcertfile.prop('disabled', true); + } + $clientcertpassphrase.prop('disabled', false); + $clientcertsource.prop('disabled', false); + } else { + $secretexpiryrecipients.prop('disabled', false); + } + }); +}; diff --git a/auth/oidc/lang/cs/auth_oidc.php b/auth/oidc/lang/cs/auth_oidc.php new file mode 100644 index 00000000000..08e46baf917 --- /dev/null +++ b/auth/oidc/lang/cs/auth_oidc.php @@ -0,0 +1,133 @@ +. + +/** + * Czech language strings. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +// phpcs:disable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:disable moodle.Files.LangFilesOrdering.UnexpectedComment + +$string['pluginname'] = 'OpenID Connect'; +$string['auth_oidcdescription'] = 'Plugin OpenID Connect poskytuje funkci jednotného přihlašování pomocí konfigurovatelných poskytovatelů identity.'; +$string['cfg_authendpoint_key'] = 'Koncový bod autorizace'; +$string['cfg_authendpoint_desc'] = 'Identifikátor URI koncového bodu autorizace od vašeho poskytovatele identity, který se má použít.'; +$string['cfg_autoappend_key'] = 'Automaticky připojit'; +$string['cfg_autoappend_desc'] = 'Automaticky připojit tento řetězec při přihlašování uživatelů pomocí postupu přihlášení uživatelské_jméno/heslo. To je užitečné, když váš poskytovatel identity požaduje společnou doménu, ale po uživatelích nechcete požadovat její psaní při přihlašování. Pokud například uživatel OpenID Connect má celé uživatelské jméno „jankovar@example.com“ a zde zadáte „@example.com“, bude uživatel jako své uživatelské jméno zadávat jen „jankovar“.
Poznámka: V případě, že existují konfliktní uživatelská jména – například že existuje uživatel v Moodlu se stejným uživatelským jménem, k určení „vítězného“ uživatele se použije priorita pluginu ověření.'; +$string['cfg_clientid_key'] = 'ID klienta'; +$string['cfg_clientid_desc'] = 'Vaše registrované ID klienta u poskytovatele identity.'; +$string['cfg_clientsecret_key'] = 'Tajný klíč klienta'; +$string['cfg_clientsecret_desc'] = 'Váš registrovaný tajný klíč klienta u poskytovatele identity. U některých poskytovatelů označovaný jen jako klíč.'; +$string['cfg_err_invalidauthendpoint'] = 'Neplatný koncový bod autorizace'; +$string['cfg_err_invalidtokenendpoint'] = 'Neplatný koncový bod tokenu'; +$string['cfg_err_invalidclientid'] = 'Neplatné ID klienta'; +$string['cfg_err_invalidclientsecret'] = 'Neplatný tajný klíč klienta'; +$string['cfg_icon_key'] = 'Ikona'; +$string['cfg_icon_desc'] = 'Ikona, která se bude zobrazovat na přihlašovací stránce vedle názvu poskytovatele.'; +$string['cfg_iconalt_o365'] = 'Ikona Microsoft 365'; +$string['cfg_iconalt_locked'] = 'Ikona uzamčení'; +$string['cfg_iconalt_lock'] = 'Ikona zámku'; +$string['cfg_iconalt_go'] = 'Zelené kolečko'; +$string['cfg_iconalt_stop'] = 'Červené kolečko'; +$string['cfg_iconalt_user'] = 'Ikona uživatele'; +$string['cfg_iconalt_user2'] = 'Alternativa ikony uživatele'; +$string['cfg_iconalt_key'] = 'Ikona klíče'; +$string['cfg_iconalt_group'] = 'Ikona skupiny'; +$string['cfg_iconalt_group2'] = 'Alternativa ikony skupiny'; +$string['cfg_iconalt_mnet'] = 'Ikona MNET'; +$string['cfg_iconalt_userlock'] = 'Ikona uživatele se zámkem'; +$string['cfg_iconalt_plus'] = 'Ikona plus'; +$string['cfg_iconalt_check'] = 'Ikona zaškrtnutí'; +$string['cfg_iconalt_rightarrow'] = 'Ikona šipky doprava'; +$string['cfg_customicon_key'] = 'Vlastní ikona'; +$string['cfg_customicon_desc'] = 'Pokud chcete použít vlastní ikonu, nahrajte ji zde. To přepíše jakékoli ikony vybrané výše.

Poznámky k používání vlastních ikon:
  • Velikost tohoto obrázku se na přihlašovací stránce nepřizpůsobí, proto doporučujeme nenahrávat obrázky větší než 35x35 pixelů.
  • Pokud jste nahráli vlastní ikonu a chtěli byste se vrátit k některé ze základních dodaných ikon, klikněte nahoře na vlastní ikonu, pak klikněte na tlačítko Odstranit, potvrďte kliknutím na OK a potom klikněte dole ve formuláři na tlačítko Uložit změny. Na přihlašovací stránce Moodlu se objeví vybraná základní ikona.
'; +$string['cfg_debugmode_key'] = 'Zaznamenávat zprávy ladění'; +$string['cfg_debugmode_desc'] = 'Pokud je toto nastavení povoleno, do protokolu Moodlu jsou zaznamenávány informace, které vám mohou pomoci identifikovat problémy.'; +$string['cfg_loginflow_key'] = 'Postup přihlášení'; +$string['cfg_loginflow_authcode'] = 'Požadavek na autorizaci'; +$string['cfg_loginflow_authcode_desc'] = 'Při použití tohoto postupu uživatel na přihlašovací stránce Moodlu klikne na ikonu poskytovatele identity (viz výše „Název poskytovatele“) a je následně přesměrován na poskytovatele, aby se přihlásil. Po úspěšném přihlášení je uživatel přesměrován zpět do Moodlu, kde proběhne transparentní přihlášení do Moodlu. Toto je nejstandardizovanější bezpečný způsob přihlašování uživatelů.'; +$string['cfg_loginflow_rocreds'] = 'Ověřování uživatelské_jméno/heslo'; +$string['cfg_loginflow_rocreds_desc'] = 'Při použití tohoto postupu uživatel zadá své uživatelské jméno a heslo do přihlašovacího formuláře Moodlu, obdobně jako při ručním přihlášení. Jeho přihlašovací údaje jsou pak na pozadí předány poskytovateli identity, aby bylo získáno ověření. Tento postup je pro uživatele nejtransparentnější, protože nemá žádný přímý kontakt s poskytovatelem identity. Ne všichni poskytovatelé identity ale tento postup podporují.'; +$string['cfg_oidcresource_key'] = 'Zdroj'; +$string['cfg_oidcresource_desc'] = 'Zdroj OpenID Connect, pro který se odesílá požadavek.'; +$string['cfg_oidcscope_key'] = 'Scope'; +$string['cfg_oidcscope_desc'] = 'Rozsah OIDC, který se má použít.'; +$string['cfg_opname_key'] = 'Název poskytovatele'; +$string['cfg_opname_desc'] = 'Toto je údaj zobrazovaný koncovému uživateli, který identifikuje, jaký typ přihlašovacích údajů potřebuje uživatel použít k přihlášení. Tento údaj se používá na více místech rozhraní pro koncového uživatele v tomto pluginu k identifikaci vašeho poskytovatele.'; +$string['cfg_redirecturi_key'] = 'URI pro přesměrování'; +$string['cfg_redirecturi_desc'] = 'Toto je identifikátor URI, který se registruje jako „URI pro přesměrování“. Váš poskytovatel identity OpenID Connect by vás měl o tento identifikátor požádat při registraci Moodlu jako klienta.
Poznámka: Tento identifikátor musíte ve svém poskytovateli identity OpenID Connect zadat *přesně* tak, jak je zde uveden. Jakákoli odchylka znemožní přihlášení s použitím OpenID Connect.'; +$string['cfg_tokenendpoint_key'] = 'Koncový bod tokenu'; +$string['cfg_tokenendpoint_desc'] = 'URI koncového bodu tokenu od vašeho poskytovatele identity, který se má použít.'; +$string['event_debug'] = 'Zpráva ladění'; +$string['errorauthdisconnectemptypassword'] = 'Heslo nemůže být prázdné.'; +$string['errorauthdisconnectemptyusername'] = 'Uživatelské jméno nemůže být prázdné.'; +$string['errorauthdisconnectusernameexists'] = 'Toto uživatelské jméno se již používá. Zvolte jiné.'; +$string['errorauthdisconnectnewmethod'] = 'Použít metodu přihlášení'; +$string['errorauthdisconnectinvalidmethod'] = 'Přijata neplatná metoda přihlášení.'; +$string['errorauthdisconnectifmanual'] = 'Pokud používáte metodu ručního přihlášení, zadejte níže přihlašovací údaje.'; +$string['errorauthinvalididtoken'] = 'Přijato neplatné id_token.'; +$string['errorauthloginfailednouser'] = 'Neplatné přihlášení: Uživatel nebyl v Moodlu nalezen.'; +$string['errorauthnoauthcode'] = 'Nebyl přijat kód ověření.'; +$string['errorauthnocreds'] = 'Nakonfigurujte přihlašovací údaje klienta OpenID Connect.'; +$string['errorauthnoendpoints'] = 'Nakonfigurujte koncové body serveru OpenID Connect.'; +$string['errorauthnohttpclient'] = 'Nastavte klienta HTTP.'; +$string['errorauthnoidtoken'] = 'Nebylo přijato id_token OpenID Connect.'; +$string['errorauthunknownstate'] = 'Neznámý stav.'; +$string['errorauthuseralreadyconnected'] = 'Jste již připojeni k jinému uživateli OpenID Connect.'; +$string['errorauthuserconnectedtodifferent'] = 'Uživatel OpenID Connect, který byl ověřen, je již připojen k uživateli Moodle.'; +$string['errorbadloginflow'] = 'Byl zadán neplatný postup přihlášení. Poznámka: Pokud se vám tato zpráva zobrazuje poté, co jste provedli instalaci nebo upgrade, vymažte mezipaměť Moodlu.'; +$string['errorjwtbadpayload'] = 'Nejde přečíst datovou část JWT.'; +$string['errorjwtcouldnotreadheader'] = 'Nelze číst hlavičku JWT.'; +$string['errorjwtempty'] = 'Přijato prázdné nebo ne-řetězec JWT.'; +$string['errorjwtinvalidheader'] = 'Neplatná hlavička JWT'; +$string['errorjwtmalformed'] = 'Přijato poškozené JWT.'; +$string['errorjwtunsupportedalg'] = 'JWS Alg nebo JWE není podporováno'; +$string['erroroidcnotenabled'] = 'Plugin ověření OpenID Connect není povolen.'; +$string['errornodisconnectionauthmethod'] = 'Nelze se odpojit, protože není povolen žádný plugin ověření, který by mohl převzít funkci. (buď uživatelova předchozí metoda přihlášení, nebo metoda ručního přihlášení).'; +$string['erroroidcclientinvalidendpoint'] = 'Přijato neplatné URI koncového bodu.'; +$string['erroroidcclientnocreds'] = 'Nastavte přihlašovací údaje klienta s tajnými klíči.'; +$string['erroroidcclientnoauthendpoint'] = 'Není nastaven žádný koncový bod autorizace. Nastavte pomocí $this->setendpoints'; +$string['erroroidcclientnotokenendpoint'] = 'Není nastaven žádný koncový bod tokenu. Nastavte pomocí $this->setendpoints'; +$string['erroroidcclientinsecuretokenendpoint'] = 'Koncový bod tokenu musí pro toto používat SSL/TLS.'; +$string['errorucpinvalidaction'] = 'Přijata neplatná akce.'; +$string['erroroidccall'] = 'Chyba v OpenID Connect. Další informace naleznete v protokolech.'; +$string['erroroidccall_message'] = 'Chyba v OpenID Connect: {$a}'; +$string['eventuserauthed'] = 'Uživatel autorizován s OpenID Connect'; +$string['eventusercreated'] = 'Uživatel vytvořen s OpenID Connect'; +$string['eventuserconnected'] = 'Uživatel připojen k OpenID Connect'; +$string['eventuserloggedin'] = 'Uživatel přihlášen s OpenID Connect'; +$string['eventuserdisconnected'] = 'Uživatel odpojen od OpenID Connect'; +$string['oidc:manageconnection'] = 'Spravovat připojení OpenID Connect'; +$string['ucp_general_intro'] = 'Zde můžete spravovat své připojení k {$a}. Pokud je nastavení povoleno, budete moci použít svůj účet {$a} k přihlášení do Moodlu namísto používání samostatného uživatelského jména a hesla. Jakmile se připojíte, nebudete si muset nadále pamatovat svoje uživatelské jméno a heslo pro Moodle, veškerá přihlášení budou probíhat přes {$a}.'; +$string['ucp_login_start'] = 'Začít používat {$a} k přihlašování do Moodlu'; +$string['ucp_login_start_desc'] = 'Toto přepne váš účet, aby k přihlašování do Moodlu používal {$a}. Jakmile nastavení povolíte, budete se přihlašovat se svými přihlašovacími údaji {$a} – vaše aktuální uživatelské jméno a heslo pro Moodle nebudou fungovat. Kdykoli můžete svůj účet odpojit a vrátit se k normálnímu přihlašování.'; +$string['ucp_login_stop'] = 'Přestat používat {$a} k přihlašování do Moodlu'; +$string['ucp_login_stop_desc'] = 'Aktuálně k přihlašování do Moodlu používáte {$a}. Když kliknete na „Přestat používat {$a} k přihlašování do Moodlu“, váš účet Moodle se odpojí od {$a}. Nebudete se nadále moci přihlašovat do Moodlu pomocí účtu {$a}. Bude požádáni, abyste si vytvořili uživatelské jméno a heslo, a od té chvíle se pak budete moci do Moodlu přihlašovat přímo.'; +$string['ucp_login_status'] = 'Přihlašování {$a} je:'; +$string['ucp_status_enabled'] = 'Povoleno'; +$string['ucp_status_disabled'] = 'Zakázáno'; +$string['ucp_disconnect_title'] = 'Odpojení {$a}'; +$string['ucp_disconnect_details'] = 'Váš účet Moodle bude odpojen od {$a}. Budete si muset vytvořit uživatelské jméno a heslo pro přihlašování do Moodlu.'; +$string['ucp_title'] = 'Správa {$a}'; + +// phpcs:enable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:enable moodle.Files.LangFilesOrdering.UnexpectedComment \ No newline at end of file diff --git a/auth/oidc/lang/de/auth_oidc.php b/auth/oidc/lang/de/auth_oidc.php new file mode 100644 index 00000000000..fb3bd32384e --- /dev/null +++ b/auth/oidc/lang/de/auth_oidc.php @@ -0,0 +1,133 @@ +. + +/** + * German language strings. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +// phpcs:disable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:disable moodle.Files.LangFilesOrdering.UnexpectedComment + +$string['pluginname'] = 'OpenID Connect'; +$string['auth_oidcdescription'] = 'Das Plugin OpenID Connect bietet eine Single-Sign-On-Funktion mit konfigurierbaren Identitätsprovidern.'; +$string['cfg_authendpoint_key'] = 'Autorisierungsendpunkt'; +$string['cfg_authendpoint_desc'] = 'Die URI des Autorisierungsendpunktes, dessen Verwendung Ihr Identitätsprovider vorschreibt.'; +$string['cfg_autoappend_key'] = 'Autom. anhängen'; +$string['cfg_autoappend_desc'] = 'Diese Zeichenfolge wird automatisch angehängt, wenn sich Benutzer mit dem Fluss für die Anmeldung mit Benutzernamen/Kennwort anmelden. Dies ist hilfreich, wenn Ihr Identitätsprovider eine allgemeine Domäne fordert, aber die Benutzer diese bei der Anmeldung nicht eingeben müssen. Wenn der vollständige OpenID Connect-Benutzer z. B. "james@example.com" ist und Sie geben hier "@example.com" ein, muss der Benutzer hier nicht "james" als Benutzernamen eingeben.
Hinweis: Wenn Konflikte zwischen Benutzernamen vorliegen, d. h., ein Moodle-Benutzer mit demselben Namen vorhanden ist, wird anhand der Priorität des Authentifizierungs-Plugins festgelegt, welcher Benutzer Vorrang hat.'; +$string['cfg_clientid_key'] = 'Kunden-ID'; +$string['cfg_clientid_desc'] = 'Ihre registrierte Kunden-ID beim Identitätsprovider.'; +$string['cfg_clientsecret_key'] = 'Kundengeheimnis'; +$string['cfg_clientsecret_desc'] = 'Ihr registriertes Kundengeheimnis beim Identitätsprovider. Bei manchen Providern wird er Schlüssel genannt.'; +$string['cfg_err_invalidauthendpoint'] = 'Ungültiger Autorisierungsendpunkt'; +$string['cfg_err_invalidtokenendpoint'] = 'Ungültiger Token-Endpunkt'; +$string['cfg_err_invalidclientid'] = 'Ungültige Kunden-ID'; +$string['cfg_err_invalidclientsecret'] = 'Ungültiges Kundengeheimnis'; +$string['cfg_icon_key'] = 'Symbol'; +$string['cfg_icon_desc'] = 'Ein Symbol zur Anzeige des nächsten Providernamens auf der Anmeldeseite.'; +$string['cfg_iconalt_o365'] = 'Symbol "Microsoft 365"'; +$string['cfg_iconalt_locked'] = 'Symbol "Gesperrt"'; +$string['cfg_iconalt_lock'] = 'Symbol "Sperren"'; +$string['cfg_iconalt_go'] = 'Grüner Kreis'; +$string['cfg_iconalt_stop'] = 'Roter Kreis'; +$string['cfg_iconalt_user'] = 'Symbol "Benutzer"'; +$string['cfg_iconalt_user2'] = 'Anderes Benutzersymbol'; +$string['cfg_iconalt_key'] = 'Symbol "Schlüssel"'; +$string['cfg_iconalt_group'] = 'Symbol "Gruppe"'; +$string['cfg_iconalt_group2'] = 'Anderes Gruppensymbol'; +$string['cfg_iconalt_mnet'] = 'Symbol "MNET"'; +$string['cfg_iconalt_userlock'] = 'Symbol "Benutzer mit Sperre"'; +$string['cfg_iconalt_plus'] = 'Symbol "Plus"'; +$string['cfg_iconalt_check'] = 'Symbol "Häkchen"'; +$string['cfg_iconalt_rightarrow'] = 'Symbol "Pfeil nach rechts"'; +$string['cfg_customicon_key'] = 'Symbol "Angepasst"'; +$string['cfg_customicon_desc'] = 'Wenn Sie Ihr eigenes Symbol verwenden möchten, laden Sie es hier hoch. Damit werden alle oben ausgewählten Symbole überschrieben.

Hinweise zur Verwendung von benutzerdefinierten Symbolen:
  • Dieses Bild wird nicht auf der Anmeldeseite in der Größe angepasst. Daher empfiehlt sich, nur Bilder hochzuladen, die maximal 35x35 Pixels groß sind.
  • Wenn Sie ein benutzerdefiniertes Symbol hochgeladen haben und im Feld oben doch eines der Standardsymbole auswählen möchten, klicken Sie auf "Löschen" und dann auf "OK". Klicken Sie anschließend auf "Änderungen speichern" unten in diesem Formular. Das ausgewählte Standardsymbol wir nun auf der Moodle-Anmeldeseite angezeigt.
'; +$string['cfg_debugmode_key'] = 'Debugmeldungen aufzeichnen'; +$string['cfg_debugmode_desc'] = 'Wenn diese Option aktiviert ist, werden die Informationen im Moodle-Protokoll aufgezeichnet, das bei der Erkennung von Problemen helfen kann.'; +$string['cfg_loginflow_key'] = 'Anmeldefluss'; +$string['cfg_loginflow_authcode'] = 'Autorisierungsanforderung'; +$string['cfg_loginflow_authcode_desc'] = 'Mit diesem Fluss klickt der Benutzer auf der Moodle-Anmeldeseite auf den Namen des Identitätsproviders (siehe "Providername" weiter oben) und wird zur Anmeldung zum Provider umgeleitet. Nach erfolgreicher Anmeldung wird der Benutzer zurück zu Moodle umgeleitet, wo die Moodle-Anmeldung transparent durchgeführt wird. Dies ist die am meisten standardisierte und sicherste Möglichkeit der Benutzeranmeldung.'; +$string['cfg_loginflow_rocreds'] = 'Authentifizierung mit Benutzername/Kennwort'; +$string['cfg_loginflow_rocreds_desc'] = 'Mit diesem Fluss gibt der Benutzer wie bei einer manuellen Anmeldung seinen Benutzernamen und sein Kennwort im Moodle-Anmeldeformular ein. Die Anmeldedaten werden dann im Hintergrund zur Authentifizierung an den Identitätsprovider übermittelt. Dieser Fluss ist für den Benutzer am transparentesten, da er keine direkte Interaktion mit dem Identitätsprovider hat. Alle Identitätsprovider unterstützen diesen Fluss.'; +$string['cfg_oidcresource_key'] = 'Ressource'; +$string['cfg_oidcresource_desc'] = 'Die OpenID Connect-Ressource, für die die Anfrage gesendet wird.'; +$string['cfg_oidcscope_key'] = 'Umfang'; +$string['cfg_oidcscope_desc'] = 'Der zu verwendende OIDC-Bereich.'; +$string['cfg_opname_key'] = 'Providername'; +$string['cfg_opname_desc'] = 'Hierbei handelt es sich um eine Bezeichnung für den Endbenutzer, die den Typ der Anmeldedaten kennzeichnet, die der Benutzer für die Anmeldung verwenden muss. Diese Bezeichnung wird in allen benutzerorientierten Teilen dieses Plugins zur Identifizierung Ihres Providers verwendet.'; +$string['cfg_redirecturi_key'] = 'Weiterleitungs-URI'; +$string['cfg_redirecturi_desc'] = 'Dies ist die URI, die als "Weiterleitungs-URI" registriert werden soll. Ihr OpenID Connect-Identitätsprovider muss nach dieser URI fragen, wenn Sie sich in Moodle als Kunde anmelden.
HINWEIS: Sie müssen diese Zeichenfolge *genau* wie hier angezeigt bei Ihrem OpenID Connect-Provider angeben. Jede Abweichung führt dazu, dass keine Anmeldungen mit OpenID Connect möglich sind.'; +$string['cfg_tokenendpoint_key'] = 'Token-Endpunkt'; +$string['cfg_tokenendpoint_desc'] = 'Die URI des Token-Endpunktes, dessen Verwendung Ihr Identitätsprovider vorschreibt.'; +$string['event_debug'] = 'Debug-Meldung'; +$string['errorauthdisconnectemptypassword'] = 'Das Kennwort darf nicht leer sein.'; +$string['errorauthdisconnectemptyusername'] = 'Der Benutzername darf nicht leer sein'; +$string['errorauthdisconnectusernameexists'] = 'Dieser Benutzername wurde bereits verwendet. Bitte wählen Sie einen anderen Benutzernamen.'; +$string['errorauthdisconnectnewmethod'] = 'Anmeldemethode verwenden'; +$string['errorauthdisconnectinvalidmethod'] = 'Ungültig Anmeldemethode empfangen.'; +$string['errorauthdisconnectifmanual'] = 'Wenn Sie die manuelle Anmeldemethode verwenden, geben Sie Ihre Anmeldedaten unten ein.'; +$string['errorauthinvalididtoken'] = 'Ungültigen id_token empfangen.'; +$string['errorauthloginfailednouser'] = 'Ungültige Anmeldung: Benutzer wurde nicht in Moodle gefunden.'; +$string['errorauthnoauthcode'] = 'Auth.-Code nicht empfangen.'; +$string['errorauthnocreds'] = 'Konfigurieren Sie die Anmeldedaten für den OpenID Connect-Client.'; +$string['errorauthnoendpoints'] = 'Konfigurieren Sie den Endpunkte für den OpenID Connect-Server.'; +$string['errorauthnohttpclient'] = 'Legen Sie einen HTTP-Client fest.'; +$string['errorauthnoidtoken'] = 'OpenID Connect-id_token wurde nicht empfangen.'; +$string['errorauthunknownstate'] = 'Unbekannter Status.'; +$string['errorauthuseralreadyconnected'] = 'Sie sind bereits mit einem anderen OpenID Connect-Benutzer verbunden.'; +$string['errorauthuserconnectedtodifferent'] = 'Der authentifizierte OpenID Connect-Benutzer ist bereits mit einem Moodle-Benutzer verbunden.'; +$string['errorbadloginflow'] = 'Ungültiger Anmeldefluss angegeben. Hinweis: Wenn Sie diese Meldung kurz nach einer Installation oder einem Upgrades erhalten, löschen Sie den Moodle-Cache.'; +$string['errorjwtbadpayload'] = 'JWT-Last konnte nicht gelesen werden.'; +$string['errorjwtcouldnotreadheader'] = 'JWT-Kopf konnte nicht gelesen werden.'; +$string['errorjwtempty'] = 'Empfangener JWT ist leer oder enthält keine Zeichenfolge.'; +$string['errorjwtinvalidheader'] = 'Ungültiger JWT-Kopf'; +$string['errorjwtmalformed'] = 'Empfangener JWT ist nicht wohlgeformt.'; +$string['errorjwtunsupportedalg'] = 'JWS-Alg. oder JWE wird nicht unterstützt.'; +$string['erroroidcnotenabled'] = 'Das OpenID Connect-Authentifizierungs-Plugin ist nicht aktiviert.'; +$string['errornodisconnectionauthmethod'] = 'Es kann keine Verbindung hergestellt werden, da es kein aktiviertes Authentifizierungs-Plugin gibt, auf das zurückgegriffen werden kann (entweder vorherige Nutzeranmeldemethode oder manuelle Anmeldemethode).'; +$string['erroroidcclientinvalidendpoint'] = 'Empfangene Endpunkt-URI ist ungültig.'; +$string['erroroidcclientnocreds'] = 'Legen Sie die Client-Anmeldedaten mit setcreds fest.'; +$string['erroroidcclientnoauthendpoint'] = 'Kein Autorisierungsendpunkt festgelegt. Legen Sie ihn mit $this->setendpoints fest.'; +$string['erroroidcclientnotokenendpoint'] = 'Kein Token-Endpunkt festgelegt. Legen Sie ihn mit $this->setendpoints fest.'; +$string['erroroidcclientinsecuretokenendpoint'] = 'Der Token-Endpunkt muss dazu SSL/TLS verwenden.'; +$string['errorucpinvalidaction'] = 'Empfangene Aktion ist ungültig.'; +$string['erroroidccall'] = 'Fehler in OpenID Connect. Weitere Informationen finden Sie in den Protokollen.'; +$string['erroroidccall_message'] = 'Fehler in OpenID Connect: {$a}'; +$string['eventuserauthed'] = 'Benutzer wurde mit OpenID Connect autorisiert.'; +$string['eventusercreated'] = 'Benutzer wurde mit OpenID Connect erstellt.'; +$string['eventuserconnected'] = 'Benutzer ist mit OpenID Connect verbunden.'; +$string['eventuserloggedin'] = 'Benutzer wurde mit OpenID Connect angemeldet.'; +$string['eventuserdisconnected'] = 'Benutzer ist von OpenID Connect getrennt.'; +$string['oidc:manageconnection'] = 'OpenID Connect-Verbindung verwalten'; +$string['ucp_general_intro'] = 'Hier können Sie Ihre Verbindung mit {$a} verwalten. Ist die Option deaktiviert, können Sie sich mit Ihrem {$a}-Konto bei Moodle anmelden und müssen keinen Benutzernamen und kein Kennwort eingeben. Sobald die Verbindung besteht, müssen Sie nicht mehr Ihren Benutzernamen und das Kennwort für Moodle behalten. Die gesamte Anmeldung wird von {$a} durchgeführt.'; +$string['ucp_login_start'] = 'Mit {$a} bei Moodle anmelden'; +$string['ucp_login_start_desc'] = 'Damit wird Ihr Konto umgeschaltet und es wird {$a} für die Anmeldung in Moodle verwendet. Sobald diese Option aktiviert ist, melden Sie sich mit Ihren {$a}-Anmeldedaten an. Ihr aktueller Benutzername und das Kennwort von Moodle funktionieren nicht mehr. Sie können Ihr Konto jederzeit trennen und zur normalen Anmeldung zurückkehren.'; +$string['ucp_login_stop'] = 'Verwendung von {$a} zur Anmeldung in Moodle anhalten'; +$string['ucp_login_stop_desc'] = 'Derzeit verwenden Sie {$a} für die Anmeldung bei Moodle. Wenn Sie auf "Verwendung von {$a} zur Anmeldung bei Moodle anhalten" klicken, wird Ihr Moodle-Konto von {$a} getrennt. Sie können sich nicht mehr mit Ihrem {$a}-Konto bei Moodle anmelden. Sie werden aufgefordert, einen Benutzernamen und ein Kennwort zu erstellen. Danach können Sie sich immer direkt bei Moodle anmelden.'; +$string['ucp_login_status'] = '{$a}-Anmeldung lautet:'; +$string['ucp_status_enabled'] = 'Aktiviert'; +$string['ucp_status_disabled'] = 'Deaktiviert'; +$string['ucp_disconnect_title'] = '{$a} Trennung'; +$string['ucp_disconnect_details'] = 'Damit wird Ihr Moodle-Konto von {$a} getrennt. Sie müssen einen Benutzernamen und ein Kennwort erstellen, um sich bei Moodle anzumelden.'; +$string['ucp_title'] = '{$a} Verwaltung'; + +// phpcs:enable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:enable moodle.Files.LangFilesOrdering.UnexpectedComment \ No newline at end of file diff --git a/auth/oidc/lang/en/auth_oidc.php b/auth/oidc/lang/en/auth_oidc.php new file mode 100644 index 00000000000..75409756fdd --- /dev/null +++ b/auth/oidc/lang/en/auth_oidc.php @@ -0,0 +1,499 @@ +. + +/** + * English language strings. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +defined('MOODLE_INTERNAL') || die(); + +// phpcs:disable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:disable moodle.Files.LangFilesOrdering.UnexpectedComment + +$string['pluginname'] = 'OpenID Connect'; +$string['auth_oidcdescription'] = 'The OpenID Connect authentication plugin provides single-sign-on functionality using configurable IdP.'; + +// Configuration pages. +$string['settings_page_other_settings'] = 'Other options'; +$string['settings_page_application'] = 'IdP and authentication'; +$string['settings_page_binding_username_claim'] = 'Binding Username Claim'; +$string['settings_page_change_binding_username_claim_tool'] = 'Change binding username claim tool'; +$string['settings_page_cleanup_oidc_tokens'] = 'Cleanup OpenID Connect tokens'; +$string['settings_page_field_mapping'] = 'Field mappings'; +$string['heading_basic'] = 'Basic settings'; +$string['heading_basic_desc'] = ''; +$string['heading_additional_options'] = 'Additional options'; +$string['heading_additional_options_desc'] = ''; +$string['heading_user_restrictions'] = 'User restrictions'; +$string['heading_user_restrictions_desc'] = ''; +$string['heading_sign_out'] = 'Sign out integration'; +$string['heading_sign_out_desc'] = ''; +$string['heading_display'] = 'Display'; +$string['heading_display_desc'] = ''; +$string['heading_debugging'] = 'Debugging'; +$string['heading_debugging_desc'] = ''; +$string['idptype'] = 'Identity Provider (IdP) Type'; +$string['idptype_help'] = 'Three types of IdP are currently supported: +
    +
  • Microsoft Entra ID (v1.0): Microsoft Entra ID with oauth2 v1.0 endpoints, e.g. https://login.microsoftonline.com/organizations/oauth2/authorize.
  • +
  • Microsoft identity platform (v2.0): Microsoft Entra ID with oath2 v2.0 endpoints, e.g. https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize.
  • +
  • Other: any non Microsoft IdP.
  • +
+The differences between Microsoft Entra ID (v1.0) and Microsoft identity platform (v2.0) options can be found at https://docs.microsoft.com/en-us/azure/active-directory/azuread-dev/azure-ad-endpoint-comparison.
+Notably, the configured application can use certificate besides secret for authentication when using Microsoft identity platform (v2.0) IdP.
+Authorization and token endpoints need to be configured according to the configured IdP type.'; +$string['idp_type_microsoft_entra_id'] = 'Microsoft Entra ID (v1.0)'; +$string['idp_type_microsoft_identity_platform'] = 'Microsoft identity platform (v2.0)'; +$string['idp_type_other'] = 'Other'; +$string['cfg_authenticationlink_desc'] = 'Link to IdP and authentication configuration'; +$string['authendpoint'] = 'Authorization Endpoint'; +$string['authendpoint_help'] = 'The URI of the Authorization endpoint from your IdP to use.
+Note if the site is to be configured to allow users from other tenants to access, tenant specific authorization endpoint cannot be used.'; +$string['cfg_autoappend_key'] = 'Auto-Append'; +$string['cfg_autoappend_desc'] = 'Automatically append this string when logging in users using the "Resource Owner Password Credentials" authentication method. This is useful when your IdP requires a common domain, but don\'t want to require users to type it in when logging in. For example, if the full OpenID Connect user is "james@example.com" and you enter "@example.com" here, the user will only have to enter "james" as their username.
Note: In the case where conflicting usernames exist - i.e. a Moodle user exists wth the same name, the priority of the authentication plugin is used to determine which user wins out.'; +$string['clientid'] = 'Application ID'; +$string['clientid_help'] = 'The registered Application / Client ID on the IdP.'; +$string['clientauthmethod'] = 'Client authentication method'; +$string['clientauthmethod_help'] = '
    +
  • IdP in all types can use "Secret" authentication method.
  • +
  • IdP in Microsoft identity platform (v2.0) type can additionally use Certificate authentication method.
  • +
'; +$string['auth_method_secret'] = 'Secret'; +$string['auth_method_certificate'] = 'Certificate'; +$string['clientsecret'] = 'Client Secret'; +$string['clientsecret_help'] = 'When using secret authentication method, this is the client secret on the IdP. On some providers, it is also referred to as a key.'; +$string['clientprivatekey'] = 'Client certificate private key'; +$string['clientprivatekey_help'] = 'When using certificate authentication method and Plain text certificate source, this is the private key of the certificate used to authenticate with IdP.'; +$string['clientcert'] = 'Client certificate public key'; +$string['clientcert_help'] = 'When using certificate authentication method and Plain text certificate source, this is the public key, or certificate, used in to authenticate with IdP.'; +$string['clientcertsource'] = 'Certificate source'; +$string['clientcertsource_help'] = 'When using certificate authentication method, this is used to define where to retrieve the certificate from. +
    +
  • Plain text source requires the certificate/private key file contents to be configured in the subsequent text area settings.
  • +
  • File name source requires the certificate/private key files exist in a folder microsoft_certs in the Moodle data folder.
  • +
'; +$string['cert_source_text'] = 'Plain text'; +$string['cert_source_path'] = 'File name'; +$string['clientprivatekeyfile'] = 'File name of client certificate private key'; +$string['clientprivatekeyfile_help'] = 'When using certificate authentication method and File name certificate source, this is the file name of private key used to authenticate with IdP. The file needs to present in a folder microsoft_certs in the Moodle data folder.'; +$string['clientcertfile'] = 'File name of client certificate public key'; +$string['clientcertfile_help'] = 'When using certificate authentication method and File name certificate source, this is the file name of public key, or certificate, used to authenticate with IdP. The file needs to present in a folder microsoft_certs in the Moodle data folder.'; +$string['clientcertpassphrase'] = 'Client certificate passphrase'; +$string['clientcertpassphrase_help'] = 'If the client certificate private key is encrypted, this is the passphrase to decrypt it.'; +$string['cfg_domainhint_key'] = 'Domain Hint'; +$string['cfg_domainhint_desc'] = 'When using the Authorization Code login flow, pass this value as the "domain_hint" parameter. "domain_hint" is used by some OpenID Connect IdP to make the login process easier for users. Check with your provider to see whether they support this parameter.'; +$string['cfg_err_invalidauthendpoint'] = 'Invalid Authorization Endpoint'; +$string['cfg_err_invalidtokenendpoint'] = 'Invalid Token Endpoint'; +$string['cfg_err_invalidclientid'] = 'Invalid client ID'; +$string['cfg_err_invalidclientsecret'] = 'Invalid client secret'; +$string['cfg_forceredirect_key'] = 'Force redirect'; +$string['cfg_forceredirect_desc'] = 'If enabled, will skip the login index page and redirect to the OpenID Connect page. Can be bypassed with ?noredirect=1 URL param'; +$string['cfg_icon_key'] = 'Icon'; +$string['cfg_icon_desc'] = 'An icon to display next to the provider name on the login page.'; +$string['cfg_iconalt_o365'] = 'Microsoft 365 icon'; +$string['cfg_iconalt_locked'] = 'Locked icon'; +$string['cfg_iconalt_lock'] = 'Lock icon'; +$string['cfg_iconalt_go'] = 'Green circle'; +$string['cfg_iconalt_stop'] = 'Red circle'; +$string['cfg_iconalt_user'] = 'User icon'; +$string['cfg_iconalt_user2'] = 'User icon alternate'; +$string['cfg_iconalt_key'] = 'Key icon'; +$string['cfg_iconalt_group'] = 'Group icon'; +$string['cfg_iconalt_group2'] = 'Group icon alternate'; +$string['cfg_iconalt_mnet'] = 'MNET icon'; +$string['cfg_iconalt_userlock'] = 'User with lock icon'; +$string['cfg_iconalt_plus'] = 'Plus icon'; +$string['cfg_iconalt_check'] = 'Checkmark icon'; +$string['cfg_iconalt_rightarrow'] = 'Right-facing arrow icon'; +$string['cfg_customicon_key'] = 'Custom Icon'; +$string['cfg_customicon_desc'] = 'If you\'d like to use your own icon, upload it here. This overrides any icon chosen above.

Notes on using custom icons:
  • This image will not be resized on the login page, so we recommend uploading an image no bigger than 35x35 pixels.
  • If you have uploaded a custom icon and want to go back to one of the stock icons, click the custom icon in the box above, then click "Delete", then click "OK", then click "Save Changes" at the bottom of this form. The selected stock icon will now appear on the Moodle login page.
'; +$string['cfg_debugmode_key'] = 'Record debug messages'; +$string['cfg_debugmode_desc'] = 'If enabled, information will be logged to the Moodle log that can help in identifying problems.'; +$string['cfg_loginflow_key'] = 'Login Flow'; +$string['cfg_loginflow_authcode'] = 'Authorization Code Flow (recommended)'; +$string['cfg_loginflow_authcode_desc'] = 'Using this flow, the user clicks the name of the IdP (See "Provider Display Name" above) on the Moodle login page and is redirected to the provider to log in. Once successfully logged in, the user is redirected back to Moodle where the Moodle login takes place transparently. This is the most standardized, secure way for the user log in.'; +$string['cfg_loginflow_rocreds'] = 'Resource Owner Password Credentials Grant (deprecated)'; +$string['cfg_loginflow_rocreds_desc'] = 'This login flow is deprecated and will be removed from the plugin soon.
Using this flow, the user enters their username and password into the Moodle login form like they would with a manual login. This will authorize the user with the IdP, but will not create a session on the IdP\'s site. For example, if using Microsoft 365 with OpenID Connect, the user will be logged in to Moodle but not the Microsoft 365 web applications. Using the authorization request is recommended if you want users to be logged in to both Moodle and the IdP. Note that not all IdP support this flow. This option should only be used when other authorization grant types are not available.'; +$string['cfg_silentloginmode_key'] = 'Silent Login Mode'; +$string['cfg_silentloginmode_desc'] = 'If enabled, Moodle will try to use the active session of a user authenticated to the configured authorization endpoint to log the user in.
+To use this feature, the following configurations are required: +
    +
  • Force users to log in (forcelogin) in the Site policies section is enabled.
  • +
  • Force redirect (auth_oidc/forceredirect) setting above is enabled.
  • +
+In order to avoid Moodle trying to use personal accounts or accounts from other tenants to login, it is also recommended to use tenant specific endpoints, rather than generic ones using "common" or "organization" etc. paths.
+
+For Microsoft IdPs, the user experience is as follows: +
    +
  • If no active user session is found, Moodle login page will show.
  • +
  • If only one active user session is found, and the user has access to the Entra ID app (i.e. user is from the same tenant, or is a guest user of the tenant), the user will be logged in to Moodle automatically using SSO.
  • +
  • If only one active user session is found, but the user doesn\'t have access to the Entra ID app (e.g. the user is from a different tenant, or the app requires user assignment and the user isn\'t assigned), the Moodle login page will show.
  • +
  • If there are multiple active user sessions who have access to the Entra ID app, a page will show to allow the user to select the account to log in with.
  • +
'; +$string['oidcresource'] = 'Resource'; +$string['oidcresource_help'] = 'The OpenID Connect resource for which to send the request.
+Note this is paramater is not supported in Microsoft identity platform (v2.0) IdP type.'; +$string['oidcscope'] = 'Scope'; +$string['oidcscope_help'] = 'The OIDC Scope to use.'; +$string['secretexpiryrecipients'] = 'Secret Expiry Notification Recipients'; +$string['secretexpiryrecipients_help'] = 'A comma-separated list of email addresses to send secret expiry notifications to.
+If no email address is entered, the main site administrator will be notified.'; +$string['cfg_opname_key'] = 'Provider Display Name'; +$string['cfg_opname_desc'] = 'This is an end-user-facing label that identifies the type of credentials the user must use to login. This label is used throughout the user-facing portions of this plugin to identify your provider.'; +$string['cfg_redirecturi_key'] = 'Redirect URI'; +$string['cfg_redirecturi_desc'] = 'This is the URI to register as the "Redirect URI". Your OpenID Connect IdP should ask for this when registering Moodle as a client.
NOTE: You must enter this in your OpenID Connect IdP *exactly* as it appears here. Any difference will prevent logins using OpenID Connect.'; +$string['tokenendpoint'] = 'Token Endpoint'; +$string['tokenendpoint_help'] = 'The URI of the token endpoint from your IdP to use.
+Note if the site is to be configured to allow users from other tenants to access, tenant specific token endpoint cannot be used.'; +$string['cfg_userrestrictions_key'] = 'User Restrictions'; +$string['cfg_userrestrictions_desc'] = 'Only allow users to log in that meet certain restrictions.
How to use user restrictions:
  • Enter a regular expression pattern that matches the usernames of users you want to allow.
  • Enter one pattern per line
  • If you enter multiple patterns a user will be allowed if they match ANY of the patterns.
  • The character "/" should be escaped with "\".
  • If you don\'t enter any restrictions above, all users that can log in to the OpenID Connect IdP will be accepted by Moodle.
  • Any user that does not match any entered pattern(s) will be prevented from logging in using OpenID Connect.
'; +$string['cfg_userrestrictionscasesensitive_key'] = 'User Restrictions Case Sensitive'; +$string['cfg_userrestrictionscasesensitive_desc'] = 'This controls if the "/i" option in regular expression is used in the user restriction match.
If enabled, all user restriction checks will be performed as with case sensitive. Note if this is disabled, any patterns on letter cases will be ignored.'; +$string['cfg_signoffintegration_key'] = 'Single Sign Out (from Moodle to IdP)'; +$string['cfg_signoffintegration_desc'] = 'If the option is enabled, when a Moodle user connected to the configured IdP logs out of Moodle, the integration will trigger a request at the logout endpiont below, attempting to log the user off from IdP as well.
+Note for integration with Microsoft Entra ID, the URL of Moodle site ({$a}) needs to be added as a redirect URI in the Azure app created for Moodle and Microsoft 365 integration.'; +$string['cfg_logoutendpoint_key'] = 'IdP Logout Endpoint'; +$string['cfg_logoutendpoint_desc'] = 'The URI of the logout endpoint from your IdP to use.'; +$string['cfg_frontchannellogouturl_key'] = 'Front-channel Logout URL'; +$string['cfg_frontchannellogouturl_desc'] = 'This is the URL that your IdP needs to trigger when it tries to log users out of Moodle.
+For Microsoft Entra ID / Microsoft identity platform, the setting is called "Front-channel logout URL" and is configurable in the Azure app.'; +$string['cfg_field_mapping_desc'] = 'User profile data can be mapped from Open ID Connect IdP to Moodle. The remote fields available to map heavily depends on the IdP type.
+
    +
  • Some basic profile fields are available from access token and ID token claims from all IdP types.
  • +
  • If Microsoft IdP type is configured (either v1.0 or v2.0), additional profile data can be made available via Graph API calls by installing and configuring the Microsoft 365 integration plugin (local_o365).
  • +
  • If SDS profile sync feature is enabled in the local_o365 plugin, certain profile fields can be synchronised from SDS to Moodle. when running the "Sync with SDS" scheduled task, and will not happen when running the "Sync users with Microsoft Entra ID" scheduled task, nor when user logs in.
  • +
+ +The claims available from the ID and access tokens vary depending on IdP type, but most IdP allows some level of customisation of the claims. Documentation on Microsoft IdPs are linked below: +'; + +$string['cfg_cleanupoidctokens_key'] = 'Cleanup OpenID Connect Tokens'; +$string['cfg_cleanupoidctokens_desc'] = 'If your users are experiencing problems logging in using their Microsoft 365 account, trying cleaning up OpenID Connect tokens. This removes stray and incomplete tokens that can cause errors. WARNING: This may interrupt logins in-process, so it\'s best to do this during downtime.'; +$string['settings_section_basic'] = 'Basic settings'; +$string['settings_section_authentication'] = 'Authentication'; +$string['settings_section_endpoints'] = 'Endpoints'; +$string['settings_section_binding_username_claim'] = 'Binding Username Claim'; +$string['settings_section_other_params'] = 'Other parameters'; +$string['settings_section_secret_expiry_notification'] = 'Secret expiry notification'; +$string['authentication_and_endpoints_saved'] = 'Authentication and endpoint settings updated.'; +$string['application_updated'] = 'OpenID Connect application setting have been updated.'; +$string['application_updated_microsoft'] = 'OpenID Connect application setting was updated.
+Azure administrator will need to Provide admin consent and Verify setup again on the Microsoft 365 integration configuration page if "Identity Provider (IdP) Type" or "Client authentication method" settings are updated.'; +$string['application_not_changed'] = 'OpenID Connect application setting was not changed.'; + +$string['event_debug'] = 'Debug message'; + +$string['task_cleanup_oidc_state_and_token'] = 'Clean up OIDC state and invalid token'; +$string['task_cleanup_oidc_sid'] = 'Clean up OIDC SID records'; + +$string['errorauthdisconnectemptypassword'] = 'Password cannot be empty'; +$string['errorauthdisconnectemptyusername'] = 'Username cannot be empty'; +$string['errorauthdisconnectusernameexists'] = 'That username is already taken. Please choose a different one.'; +$string['errorauthdisconnectnewmethod'] = 'Use Login Method'; +$string['errorauthdisconnectinvalidmethod'] = 'Invalid login method received.'; +$string['errorauthdisconnectifmanual'] = 'If using the manual login method, enter credentials below.'; +$string['errorauthdisconnectinvalidmethod'] = 'Invalid login method received.'; +$string['errorauthgeneral'] = 'There was a problem logging you in. Please contact your administrator for assistance.'; +$string['errorauthinvalididtoken'] = 'Invalid id_token received.'; +$string['errorauthloginfailednouser'] = 'Invalid login: User not found in Moodle. If this site has the "authpreventaccountcreation" setting enabled, this may mean you need an administrator to create an account for you first.'; +$string['errorauthloginfaileddupemail'] = 'Invalid login: An existing account on this Moodle has the same email address as the account you try to create, and "Allow accounts with same email" (allowaccountssameemail) setting is disabled.'; +$string['errorauthnoauthcode'] = 'No authorization code was received from the identity server. The error logs may have more information.'; +$string['errorauthnocredsandendpoints'] = 'Please configure OpenID Connect client credentials and endpoints.'; +$string['errorauthnohttpclient'] = 'Please set an HTTP client.'; +$string['errorauthnoidtoken'] = 'OpenID Connect id_token not received.'; +$string['errorauthnoaccesstoken'] = 'Access token not received.'; +$string['errorauthunknownstate'] = 'Unknown state.'; +$string['errorauthuseralreadyconnected'] = 'You\'re already connected to a different OpenID Connect user.'; +$string['errorauthuserconnectedtodifferent'] = 'The OpenID Connect user that authenticated is already connected to a Moodle user.'; +$string['errorbadloginflow'] = 'Invalid authentication type specified. Note: If you are receiving this after a recent installation or upgrade, please clear your Moodle cache.'; +$string['errorjwtbadpayload'] = 'Could not read JWT payload.'; +$string['errorjwtcouldnotreadheader'] = 'Could not read JWT header'; +$string['errorjwtempty'] = 'Empty or non-string JWT received.'; +$string['errorjwtinvalidheader'] = 'Invalid JWT header'; +$string['errorjwtmalformed'] = 'Malformed JWT received.'; +$string['errorjwtunsupportedalg'] = 'JWS Alg or JWE not supported'; +$string['errorlogintoconnectedaccount'] = 'This Microsoft 365 user is connected to a Moodle account, but OpenID Connect login is not enabled for this Moodle account. Please log in to the Moodle account using the account\'s defined authentication method to use Microsoft 365 features'; +$string['erroroidcnotenabled'] = 'The OpenID Connect authentication plugin is not enabled.'; +$string['errornodisconnectionauthmethod'] = 'Cannot disconnect because there is no enabled authentication plugin to fall back to. (either user\'s previous login method or the manual login method).'; +$string['erroroidcclientinvalidendpoint'] = 'Invalid Endpoint URI received.'; +$string['erroroidcclientnocreds'] = 'Please set client credentials with setcreds'; +$string['erroroidcclientnoauthendpoint'] = 'No authorization endpoint set. Please set with $this->setendpoints'; +$string['erroroidcclientnotokenendpoint'] = 'No token endpoint set. Please set with $this->setendpoints'; +$string['erroroidcclientinsecuretokenendpoint'] = 'The token endpoint must be using SSL/TLS for this.'; +$string['errorrestricted'] = 'This site has restrictions in place on the users that can log in with OpenID Connect. These restrictions currently prevent you from completing this login attempt.'; +$string['errorucpinvalidaction'] = 'Invalid action received.'; +$string['erroroidccall'] = 'Error in OpenID Connect. Please check logs for more information.'; +$string['erroroidccall_message'] = 'Error in OpenID Connect: {$a}'; +$string['errorinvalidredirect_message'] = 'The URL you are trying to redirect to does not exist.'; +$string['errorinvalidcertificatesource'] = 'Invalid certificate source'; +$string['error_empty_tenantnameorguid'] = 'Tenant name or GUID cannot be empty when using Microsoft Entra ID (v1.0) or Microsoft identity platform (v2.0) IdPs.'; +$string['error_invalid_client_authentication_method'] = "Invalid client authentication method"; +$string['error_empty_client_secret'] = 'Client secret cannot be empty when using "secret" authentication method'; +$string['error_empty_client_private_key'] = 'Client certificate private key cannot be empty when using "certificate" authentication method'; +$string['error_empty_client_cert'] = 'Client certificate public key cannot be empty when using "certificate" authentication method'; +$string['error_empty_client_private_key_file'] = 'Client certificate private key file cannot be empty when using "certificate" authentication method'; +$string['error_empty_client_cert_file'] = 'Client certificate public key file cannot be empty when using "certificate" authentication method'; +$string['error_empty_tenantname_or_guid'] = 'Tenant name or GUID cannot be empty when using "certificate" authentication method'; +$string['error_endpoint_mismatch_auth_endpoint'] = 'The configured authorization endpoint does not match configured IdP type.
+
    +
  • When using "Microsoft Entra ID (v1.0)" IdP type, use v1.0 endpoint, e.g. https://login.microsoftonline.com/organizations/oauth2/authorize
  • +
  • When using "Microsoft identity platform (v2.0)" IdP type, use v2.0 endpoint, e.g. https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize
  • +
'; +$string['error_endpoint_mismatch_token_endpoint'] = 'The configured token endpoint does not match configured IdP type.
+
    +
  • When using "Microsoft Entra ID (v1.0)" IdP type, use v1.0 endpoint, e.g. https://login.microsoftonline.com/organizations/oauth2/token
  • +
  • When using "Microsoft identity platform (v2.0)" IdP type, use v2.0 endpoint, e.g. https://login.microsoftonline.com/organizations/oauth2/v2.0/token
  • +
'; +$string['error_tenant_specific_endpoint_required'] = 'When using "Microsoft identity platform (v2.0)" IdP type and "Certificate" authentication method, tenant specific endpoint (i.e. not common/organizations/consumers) is required.'; +$string['error_empty_oidcresource'] = 'Resource cannot be empty when using Microsoft Entra ID (v1.0) or other types of IdP.'; +$string['erroruserwithusernamealreadyexists'] = 'Error occurred when trying to rename your Moodle account. A Moodle user with the new username already exists. Ask your site administrator to resolve this first.'; +$string['error_no_response_available'] = 'No responses available.'; + +$string['eventuserauthed'] = 'User Authorized with OpenID Connect'; +$string['eventusercreated'] = 'User created with OpenID Connect'; +$string['eventuserconnected'] = 'User connected to OpenID Connect'; +$string['eventuserloggedin'] = 'User Logged In with OpenID Connect'; +$string['eventuserdisconnected'] = 'User disconnected from OpenID Connect'; +$string['eventuserrenameattempt'] = 'The auth_oidc plugin attempted to rename a user'; + +$string['oidc:manageconnection'] = 'Allow OpenID Connection and Disconnection'; +$string['oidc:manageconnectionconnect'] = 'Allow OpenID Connection'; +$string['oidc:manageconnectiondisconnect'] = 'Allow OpenID Disconnection'; + +$string['privacy:metadata:auth_oidc'] = 'OpenID Connect Authentication'; +$string['privacy:metadata:auth_oidc_prevlogin'] = 'Previous login methods to undo Microsoft 365 connections'; +$string['privacy:metadata:auth_oidc_prevlogin:userid'] = 'The ID of the Moodle user'; +$string['privacy:metadata:auth_oidc_prevlogin:method'] = 'The previous login method'; +$string['privacy:metadata:auth_oidc_prevlogin:password'] = 'The previous (encrypted) user password field.'; +$string['privacy:metadata:auth_oidc_token'] = 'OpenID Connect tokens'; +$string['privacy:metadata:auth_oidc_token:oidcuniqid'] = 'The OIDC unique user identifier.'; +$string['privacy:metadata:auth_oidc_token:username'] = 'The username of the Moodle user'; +$string['privacy:metadata:auth_oidc_token:userid'] = 'The user ID of the Moodle user'; +$string['privacy:metadata:auth_oidc_token:oidcusername'] = 'The username of the OIDC user'; +$string['privacy:metadata:auth_oidc_token:useridentifier'] = 'The user identifier of the OIDC user'; +$string['privacy:metadata:auth_oidc_token:scope'] = 'The scope of the token'; +$string['privacy:metadata:auth_oidc_token:tokenresource'] = 'The resource of the token'; +$string['privacy:metadata:auth_oidc_token:authcode'] = 'The auth code for the token'; +$string['privacy:metadata:auth_oidc_token:token'] = 'The token'; +$string['privacy:metadata:auth_oidc_token:expiry'] = 'The token expiry'; +$string['privacy:metadata:auth_oidc_token:refreshtoken'] = 'The refresh token'; +$string['privacy:metadata:auth_oidc_token:idtoken'] = 'The ID token'; + +// In the following strings, $a refers to a customizable name for the identity manager. For example, this could be +// "Microsoft 365", "OpenID Connect", etc. +$string['ucp_general_intro'] = 'Here you can manage your connection to {$a}. If enabled, you will be able to use your {$a} account to log in to Moodle instead of a separate username and password. Once connected, you\'ll no longer have to remember a username and password for Moodle, all log-ins will be handled by {$a}.'; +$string['ucp_login_start'] = 'Start using {$a} to log in to Moodle'; +$string['ucp_login_start_desc'] = 'This will switch your account to use {$a} to log in to Moodle. Once enabled, you will log in using your {$a} credentials - your current Moodle username and password will not work. You can disconnect your account at any time and return to logging in normally.'; +$string['ucp_login_stop'] = 'Stop using {$a} to log in to Moodle'; +$string['ucp_login_stop_desc'] = 'You are currently using {$a} to log in to Moodle. Clicking "Stop using {$a} login" will disconnect your Moodle account from {$a}. You will no longer be able to log in to Moodle with your {$a} account. You\'ll be asked to create a username and password, and from then on you will then be able to log in to Moodle directly.'; +$string['ucp_login_status'] = '{$a} login is:'; +$string['ucp_status_enabled'] = 'Enabled'; +$string['ucp_status_disabled'] = 'Disabled'; +$string['ucp_disconnect_title'] = '{$a} Disconnection'; +$string['ucp_disconnect_details'] = 'This will disconnect your Moodle account from {$a}. You\'ll need to create a username and password to log in to Moodle.'; +$string['ucp_title'] = '{$a} Management'; +$string['ucp_o365accountconnected'] = 'This Microsoft 365 account is already connected with another Moodle account.'; + +// Clean up OIDC tokens. +$string['cleanup_oidc_tokens'] = 'Cleanup OpenID Connect tokens'; +$string['unmatched'] = 'Unmatched'; +$string['delete_token'] = 'Delete token'; +$string['mismatched'] = 'Mismatched'; +$string['na'] = 'n/a'; +$string['mismatched_details'] = 'Token record contains username "{$a->tokenusername}"; matched Moodle user has username "{$a->moodleusername}".'; +$string['delete_token_and_reference'] = 'Delete token and reference'; +$string['table_token_id'] = 'Token record ID'; +$string['table_oidc_username'] = 'OIDC username'; +$string['table_oidc_unique_identifier'] = 'OIDC unique identifier'; +$string['table_token_unique_id'] = 'OIDC unique ID'; +$string['table_matching_status'] = 'Matching status'; +$string['table_matching_details'] = 'Details'; +$string['table_action'] = 'Action'; +$string['token_deleted'] = 'Token was deleted successfully'; +$string['no_token_to_cleanup'] = 'There are no OIDC token to cleanup.'; + +$string['errorusermatched'] = 'The Microsoft 365 account "{$a->entraidupn}" is already matched with Moodle user "{$a->username}". To complete the connection, please log in as that Moodle user first and follow the instructions in the Microsoft block.'; + +// User mapping options. +$string['update_oncreate_and_onlogin'] = 'On creation and every login'; +$string['update_oncreate_and_onlogin_and_usersync'] = 'On creation, every login, and every user sync task run'; +$string['update_onlogin_and_usersync'] = 'On every login and every user sync task run'; + +// Remote fields. +$string['settings_fieldmap_feild_not_mapped'] = '(not mapped)'; +$string['settings_fieldmap_field_bindingusernameclaim'] = 'Binding Username Claim (can only be mapped during login)'; +$string['settings_fieldmap_field_city'] = 'City'; +$string['settings_fieldmap_field_companyName'] = 'Company Name'; +$string['settings_fieldmap_field_objectId'] = 'Object ID'; +$string['settings_fieldmap_field_country'] = 'Country'; +$string['settings_fieldmap_field_department'] = 'Department'; +$string['settings_fieldmap_field_displayName'] = 'Display Name'; +$string['settings_fieldmap_field_surname'] = 'Surname'; +$string['settings_fieldmap_field_faxNumber'] = 'Fax Number'; +$string['settings_fieldmap_field_telephoneNumber'] = 'Telephone Number'; +$string['settings_fieldmap_field_givenName'] = 'Given Name'; +$string['settings_fieldmap_field_jobTitle'] = 'Job Title'; +$string['settings_fieldmap_field_mail'] = 'Email'; +$string['settings_fieldmap_field_mobile'] = 'Mobile'; +$string['settings_fieldmap_field_postalCode'] = 'Postal Code'; +$string['settings_fieldmap_field_preferredLanguage'] = 'Language'; +$string['settings_fieldmap_field_state'] = 'State'; +$string['settings_fieldmap_field_streetAddress'] = 'Street Address'; +$string['settings_fieldmap_field_userPrincipalName'] = 'User Principal Name'; +$string['settings_fieldmap_field_employeeId'] = 'Employee ID'; +$string['settings_fieldmap_field_businessPhones'] = 'Office phone'; +$string['settings_fieldmap_field_mobilePhone'] = 'Mobile phone'; +$string['settings_fieldmap_field_officeLocation'] = 'Office'; +$string['settings_fieldmap_field_preferredName'] = 'Preferred Name'; +$string['settings_fieldmap_field_manager'] = 'Manager name'; +$string['settings_fieldmap_field_manager_email'] = 'Manager email'; +$string['settings_fieldmap_field_teams'] = 'Teams'; +$string['settings_fieldmap_field_groups'] = 'Groups'; +$string['settings_fieldmap_field_roles'] = 'Roles'; +$string['settings_fieldmap_field_onPremisesSamAccountName'] = 'On-premises SAM account name'; +$string['settings_fieldmap_field_extensionattribute'] = 'Extension attribute {$a}'; +$string['settings_fieldmap_field_sds_school_id'] = 'SDS school ID ({$a})'; +$string['settings_fieldmap_field_sds_school_name'] = 'SDS school name ({$a})'; +$string['settings_fieldmap_field_sds_school_role'] = 'SDS school role ("Student" or "Teacher")'; +$string['settings_fieldmap_field_sds_student_externalId'] = 'SDS student external ID'; +$string['settings_fieldmap_field_sds_student_birthDate'] = 'SDS student birth date'; +$string['settings_fieldmap_field_sds_student_grade'] = 'SDS student grade'; +$string['settings_fieldmap_field_sds_student_graduationYear'] = 'SDS student graduation year'; +$string['settings_fieldmap_field_sds_student_studentNumber'] = 'SDS student number'; +$string['settings_fieldmap_field_sds_teacher_externalId'] = 'SDS teacher external ID'; +$string['settings_fieldmap_field_sds_teacher_teacherNumber'] = 'SDS teacher number'; + +// Binding username claim options. +$string['binding_username_claim_heading'] = 'Binding Username Claim'; +$string['binding_username_claim_description'] = '

This is an advanced feature!

+

This page allows site administrators to select the token claim to use for binding with Moodle username.

+

Be very cautious when changing this setting. Follow the steps below to change this setting on Moodle sites with existing users using OpenID Connect authentication method. Failure to do so may result in users being logged out and/or duplicate accounts being created.

+
    +
  1. Make sure you have a manual site administrator account, i.e. not using OpenID Connect authentication method.
  2. +
  3. Schedule enough downtime and put the Moodle site into maintenance mode.
  4. +
  5. Backup Moodle database, in particular user and auth_oidc_tokens tables. If local_o365 plugin is installed, backup local_o365_objects table too.
  6. +
  7. Use the update binding username tool to update Moodle username, auth_oidc token, and other connection records of the existing user to match the value of the claim to be changed to.
  8. +
  9. Update the binding username token setting on this page.
  10. +
  11. Purge caches.
  12. +
  13. Move the Moodle site out of maintenance mode.
  14. +
+

In most cases this setting should be set to the default option "Choose automatically", meaning the plugin will try to determine the token to use depending on IdP type. Misconfiguration or unexpected change of this setting will result in SSO failure.

'; +$string['binding_username_claim_description_existing_claims'] = 'The following claims are present in existing user ID tokens. Choose claims not on the list may results in SSO failure.
+
{$a}
'; +$string['binding_username_auto'] = 'Choose automatically'; +$string['binding_username_custom'] = 'Custom'; +$string['bindingusernameclaim'] = 'Binding username claim'; +$string['customclaimname'] = 'Custom claim name'; +$string['customclaimname_description'] = 'This field is used only when the Binding Username Claim setting is set to Custom.'; +$string['binding_username_claim_help_ms_no_user_sync'] = 'The options for non Microsoft IdPs include: +
    +
  • Choose automatically: Uses current logic, determining the token by IdP type and falling back to sub if no claim is found.
  • +
  • preferred_username: Default for Microsoft identity platform (v2.0) IdP type. Does not support user sync.
  • +
  • email: Fallback for Microsoft identity platform (v2.0).
  • +
  • upn: Default for Microsoft Entra ID (v1.0) and other IdP types.
  • +
  • unique_name: Fallback for Microsoft Entra ID (v1.0) and other IdP types. Does not support user sync.
  • +
  • oid: Fallback if no other claims are present. Only present in Microsoft IdP.
  • +
  • sub: Fallback if no other claims are present. Does not support user sync.
  • +
  • samaccountname: Custom claim.
  • +
  • Custom: Allows the site admin to enter a custom value. Does not support user sync.
  • +
+Note some options do not support user sync.'; +$string['binding_username_claim_help_ms_with_user_sync'] = 'The options for Microsoft IdP with user sync feature enabled include: +
    +
  • Choose automatically: Uses current logic, determining the token by IdP type and falling back to sub if no claim is found.
  • +
  • email: Fallback for Microsoft identity platform (v2.0).
  • +
  • upn: Default for Microsoft Entra ID (v1.0) and other IdP types.
  • +
  • oid: Fallback if no other claims are present. Only present in Microsoft IdP.
  • +
  • samaccountname: Custom claim.
  • +
'; +$string['binding_username_claim_help_non_ms'] = 'The options for Microsoft IdP without user sync feature enabled include: +
    +
  • Choose automatically: Uses current logic, determining the token by IdP type and falling back to sub if no claim is found.
  • +
  • preferred_username
  • +
  • email
  • +
  • unique_name
  • +
  • sub
  • +
  • samaccountname
  • +
  • custom: Custom claim.
  • +
'; +$string['binding_username_claim_updated'] = 'Binding Username Claim was updated successfully.'; +$string['examplecsv'] = 'Example upload file'; +$string['usernamefile'] = 'File'; +$string['csvdelimiter'] = 'CSV separator'; +$string['encoding'] = 'Encoding'; +$string['rowpreviewnum'] = 'Preview rows'; +$string['upload_usernames'] = 'Update binding usernames'; +$string['update_stats_users_updated'] = '{$a} users were updated'; +$string['update_stats_users_errors'] = '{$a} users had errors'; +$string['update_error_incomplete_line'] = 'The line does not contain required fields.'; +$string['update_error_user_not_found'] = 'No user found matching the username. Will try update manually matched user.'; +$string['update_error_user_not_oidc'] = 'The user is not using OpenID Connect authentication method. Will try update manually matched user.'; +$string['update_error_invalid_new_username'] = 'New username is invalid.'; +$string['update_error_user_update_failed'] = 'Failed to update user.'; +$string['update_warning_email_match'] = 'Email matches existing user.'; +$string['update_success_username'] = 'Username updated successfully.'; +$string['update_success_token'] = 'Token updated successfully.'; +$string['update_success_o365'] = 'Microsoft 365 connection record updated successfully.'; +$string['update_error_nothing_updated'] = 'Nothing was updated.'; +$string['error_invalid_upload_file'] = 'Invalid upload file.'; +$string['csvline'] = 'CSV line'; +$string['change_binding_username_claim_tool'] = 'Change binding username claim tool'; +$string['change_binding_username_claim_tool_description'] = '

This is an advanced feature!

+

This tool allows site administrators to bulk update the following records:

+
    +
  • Moodle account usernames,
  • +
  • Binding usernames in stored OpenID Connect ID tokens,
  • +
  • Moodle and Microsoft account connection records.
  • +
+

This should only be used when changing the Binding username claim settings.

+

Be very cautious when using this feature, and follow the steps on the Binding username claim configuration page. Misuse of this tool will result in Moodle user records being damaged and/or SSO failure.

+

The tool accepts a simple CSV file with two columns:

+
    +
  • username: The current username of the Moodle account to be updated, or if the current user is manually matched, this needs to be the current binding claim value.
  • +
  • new_username: The case-sensitive value of the new token claim to be used as the binding username claim. If the user is automatically matched and uses the OpenID Connect authentication type, the lowercase of this value will be used as Moodle username.
  • +
+

When the file is uploaded, the tool will perform the following actions:

+
    +
  1. Find an existing Moodle user with the given username as either username or email address, and using the OpenID Connect authentication method, and if one is found, update the username of the user to be the lowercase of new_username.
  2. +
  3. Update OpenID Connect token record. +
      +
    • If a user is found in the step 1 above, then find the token record in the auth_oidc_token table for the user, and update username column to be the lowercase of new_username, and oidcusername column to be the same as new_username.
    • +
    • If no record is found above, it will try to find record in the auth_oidc_token with oidcusername column matching the old username, and update it to be newusername.
    • +
    +
  4. Providing the local_365 plugin is installed, update user connection record. +
      +
    • If a user is found in stpe 1 above, then find the connection record of the user in the local_o365_objects table, and update the o365name column to be the same as new_username.
    • +
    • If no user is found in step 1, then it will try to find a record for a user in local_o365_objects table with o365name matching the username value, and update it to be newusername value.
    • +
    +
+

The example file below would change the binding username claim from upn or email to oid.

'; +$string['change_binding_username_claim_tool_result'] = 'Update results'; +$string['update_username_results'] = 'Update username results'; +$string['new_username'] = 'New username'; +$string['missing_idp_type'] = 'This configuration is only available if an IdP type is configured.'; + +// phpcs:enable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:enable moodle.Files.LangFilesOrdering.UnexpectedComment \ No newline at end of file diff --git a/auth/oidc/lang/es/auth_oidc.php b/auth/oidc/lang/es/auth_oidc.php new file mode 100644 index 00000000000..d77020ff8af --- /dev/null +++ b/auth/oidc/lang/es/auth_oidc.php @@ -0,0 +1,133 @@ +. + +/** + * Spanish language strings. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +// phpcs:disable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:disable moodle.Files.LangFilesOrdering.UnexpectedComment + +$string['pluginname'] = 'OpenID Connect'; +$string['auth_oidcdescription'] = 'El complemento OpenID Connect ofrece funcionalidad de inicio de sesión único a través de proveedores de identidad configurables.'; +$string['cfg_authendpoint_key'] = 'Extremo de autorización'; +$string['cfg_authendpoint_desc'] = 'La URI del extremo de autorización del proveedor de identidad que va a utilizar.'; +$string['cfg_autoappend_key'] = 'Anexo automático'; +$string['cfg_autoappend_desc'] = 'Anexe automáticamente esta cadena cuando los usuarios inicien sesión mediante el flujo de inicio de sesión de nombre de usuario/contraseña. Esto es útil cuando el proveedor de identidad requiere un dominio común, pero no desea solicitar a los usuarios que lo escriban cuando inician sesión. Por ejemplo, si el usuario completo de OpenID Connect es "james@example.com" y usted escribe "@example.com" aquí, el usuario solo deberá escribir "james" como nombre de usuario.
Nota: En el caso que existan conflictos con los nombres de usuarios (es decir, un usuario de Moodle ya existe con el mismo nombre), se utiliza la prioridad del complemento de autenticación para determinar cuál de los usuarios gana.'; +$string['cfg_clientid_key'] = 'ID de cliente'; +$string['cfg_clientid_desc'] = 'Su ID de cliente registrado en el proveedor de identidad'; +$string['cfg_clientsecret_key'] = 'Secreto de cliente'; +$string['cfg_clientsecret_desc'] = 'Su secreto de cliente registrado en el proveedor de identidad. En algunos proveedores, también se conoce como clave.'; +$string['cfg_err_invalidauthendpoint'] = 'Extremo de autorización no válido'; +$string['cfg_err_invalidtokenendpoint'] = 'Extremo de ficha no válido'; +$string['cfg_err_invalidclientid'] = 'ID de cliente no válido'; +$string['cfg_err_invalidclientsecret'] = 'Secreto de cliente no válido'; +$string['cfg_icon_key'] = 'Icono'; +$string['cfg_icon_desc'] = 'Un icono para mostrar junto al nombre del proveedor en la página de inicio de sesión.'; +$string['cfg_iconalt_o365'] = 'Icono de Microsoft 365'; +$string['cfg_iconalt_locked'] = 'Icono de bloqueado'; +$string['cfg_iconalt_lock'] = 'Icono de bloqueo'; +$string['cfg_iconalt_go'] = 'Círculo verde'; +$string['cfg_iconalt_stop'] = 'Círculo rojo'; +$string['cfg_iconalt_user'] = 'Icono de usuario'; +$string['cfg_iconalt_user2'] = 'Icono de usuario alternativo'; +$string['cfg_iconalt_key'] = 'Icono de clave'; +$string['cfg_iconalt_group'] = 'Icono de grupo'; +$string['cfg_iconalt_group2'] = 'Icono de grupo alternativo'; +$string['cfg_iconalt_mnet'] = 'Icono de MNET'; +$string['cfg_iconalt_userlock'] = 'Icono de usuario con bloqueo'; +$string['cfg_iconalt_plus'] = 'Icono de Plus'; +$string['cfg_iconalt_check'] = 'Icono de marca de verificación'; +$string['cfg_iconalt_rightarrow'] = 'Icono de flecha a la derecha'; +$string['cfg_customicon_key'] = 'Icono personalizado'; +$string['cfg_customicon_desc'] = 'Si desea utilizar su propio icono, cárguelo aquí. Esto anula cualquier icono que haya elegido arriba.

Notas sobre el uso de iconos personalizados:
  • Esta imagen no cambiará de tamaño en la página de inicio de sesión, de manera que le recomendamos que cargue una imagen no mayor a 35x35 píxeles.
  • Si cargó un icono personalizado y desea volver a algunos de los iconos preestablecidos, haga clic en el icono personalizado en el cuadro de arriba, luego haga clic en "Eliminar", luego en "Aceptar", y luego en "Guardar cambios" en la parte inferior de este formulario. El icono preestablecido seleccionado aparecerá ahora en la página de inicio de sesión de Moodle.
'; +$string['cfg_debugmode_key'] = 'Registrar mensajes de depuración'; +$string['cfg_debugmode_desc'] = 'Si está habilitado, se registrará información en el registro de Moodle que puede ayudarlo a identificar problemas.'; +$string['cfg_loginflow_key'] = 'Flujo de inicio de sesión'; +$string['cfg_loginflow_authcode'] = 'Solicitud de autorización'; +$string['cfg_loginflow_authcode_desc'] = 'Al utiliza este flujo, el usuario hace clic en el nombre del proveedor de identidad (consulte "Nombre del proveedor" más arriba) en la página de inicio de sesión de Moodle y es redireccionado al proveedor para iniciar sesión. Una vez que haya iniciado sesión correctamente, es redireccionado de vuelta a Moodle, donde se realiza el inicio de sesión de manera transparente. Esta es la forma más segura y estandarizada de inicio de sesión del usuario.'; +$string['cfg_loginflow_rocreds'] = 'Autenticación de nombre de usuario/contraseña'; +$string['cfg_loginflow_rocreds_desc'] = 'Al usar este flujo, el usuario ingresa el nombre de usuario y la contraseña al formulario de inicio de sesión de Moodle como lo haría de manera manual. Luego, las credenciales se pasan al proveedor de identidad en el segundo plano para obtener la autenticación. Este flujo es la forma más transparente para el usuario ya que no posee interacción directa con el proveedor de identidad. Tenga en cuenta que no todos los proveedores de identidad admiten este flujo.'; +$string['cfg_oidcresource_key'] = 'Recurso'; +$string['cfg_oidcresource_desc'] = 'El recurso de OpenID Connect para el cual enviar la solicitud.'; +$string['cfg_oidcscope_key'] = 'Scope'; +$string['cfg_oidcscope_desc'] = 'El alcance de OIDC a utilizar.'; +$string['cfg_opname_key'] = 'Nombre del proveedor'; +$string['cfg_opname_desc'] = 'Esta es una etiqueta que apunta al usuario final e identifica el tipo de credenciales que el usuario debe utilizar para iniciar sesión. Esta etiqueta se utiliza durante todas las partes que apuntan al usuario de este complemento para identificar al proveedor.'; +$string['cfg_redirecturi_key'] = 'URI de redireccionamiento'; +$string['cfg_redirecturi_desc'] = 'Est es la URI para registrar como "URI de redireccionamiento". Su proveedor de identidad de OpenID Connect debe solicitarla cuando registra Moodle como cliente.
NOTA: Debe ingresarla en su proveedor de OpenID Connect *exactamente* como aparece aquí. Cualquier diferencia evitará el inicio de sesión usando OpenID Connect.'; +$string['cfg_tokenendpoint_key'] = 'Extremo de ficha'; +$string['cfg_tokenendpoint_desc'] = 'La URI del extremo de ficha del proveedor de identidad que debe utilizar.'; +$string['event_debug'] = 'Mensaje de depuración'; +$string['errorauthdisconnectemptypassword'] = 'La contraseña no puede estar vacía'; +$string['errorauthdisconnectemptyusername'] = 'El nombre de usuario no puede estar vacío'; +$string['errorauthdisconnectusernameexists'] = 'Ese nombre de usuario ya está en uso. Elija uno distinto.'; +$string['errorauthdisconnectnewmethod'] = 'Usar método de inicio de sesión'; +$string['errorauthdisconnectinvalidmethod'] = 'Se recibió un método de inicio de sesión no válido.'; +$string['errorauthdisconnectifmanual'] = 'Si utiliza un método de inicio de sesión manual, ingrese las credenciales a continuación.'; +$string['errorauthinvalididtoken'] = 'Se recibió un id_token no válido.'; +$string['errorauthloginfailednouser'] = 'Inicio de sesión no válido: no se encontró el usuario en Moodle.'; +$string['errorauthnoauthcode'] = 'No se recibió el código de autenticación.'; +$string['errorauthnocreds'] = 'Configure las credenciales del cliente de OpenID Connect.'; +$string['errorauthnoendpoints'] = 'Configure los extremos del servidor de OpenID Connect.'; +$string['errorauthnohttpclient'] = 'Establezca un cliente de HTTP.'; +$string['errorauthnoidtoken'] = 'No se recibió el id_token de OpenID Connect.'; +$string['errorauthunknownstate'] = 'Estado desconocido.'; +$string['errorauthuseralreadyconnected'] = 'Ya está conectado a un usuario distinto de OpenID Connect.'; +$string['errorauthuserconnectedtodifferent'] = 'El usuario de OpenID Connect que autenticó ya está conectado al usuario de Moodle.'; +$string['errorbadloginflow'] = 'Se especificó un flujo de inicio de sesión no válido. Nota: si recibió esto después de una instalación o actualización reciente, borre el caché de Moodle.'; +$string['errorjwtbadpayload'] = 'No se pudo leer la carga de pago de JWT.'; +$string['errorjwtcouldnotreadheader'] = 'No se pudo leer el encabezado de JWT'; +$string['errorjwtempty'] = 'Se recibió un JWT vacío o que no es cadena.'; +$string['errorjwtinvalidheader'] = 'Encabezado de JWT no válido'; +$string['errorjwtmalformed'] = 'Se recibió un JWT incorrecto.'; +$string['errorjwtunsupportedalg'] = 'JWS Alg o JWE no compatible'; +$string['erroroidcnotenabled'] = 'El complemento de autenticación de OpenID Connect no está habilitado.'; +$string['errornodisconnectionauthmethod'] = 'No se puede desconectar porque no hay un complemento de autenticación habilitado para volver (el método de inicio de sesión anterior del usuario o el método de inicio de sesión manual).'; +$string['erroroidcclientinvalidendpoint'] = 'Se recibió una URI de extremo no válida.'; +$string['erroroidcclientnocreds'] = 'Establezca las credenciales del cliente con secretos'; +$string['erroroidcclientnoauthendpoint'] = 'No se configuró el extremo de autorización. Configúrelo con $this->setendpoints'; +$string['erroroidcclientnotokenendpoint'] = 'No se configuró el extremo de ficha. Configúrelo con $this->setendpoints'; +$string['erroroidcclientinsecuretokenendpoint'] = 'El extremo de ficha debe utilizar SSL/TLS para esto.'; +$string['errorucpinvalidaction'] = 'Se recibió una acción no válida.'; +$string['erroroidccall'] = 'Error en OpenID Connect. Revise los registros para obtener más información.'; +$string['erroroidccall_message'] = 'Error en OpenID Connect: {$a}'; +$string['eventuserauthed'] = 'Usuario autorizado con OpenID Connect'; +$string['eventusercreated'] = 'Usuario creado con OpenID Connect'; +$string['eventuserconnected'] = 'Usuario conectado a OpenID Connect'; +$string['eventuserloggedin'] = 'Usuario inició sesión con OpenID Connect'; +$string['eventuserdisconnected'] = 'Usuario desconectado de OpenID Connect'; +$string['oidc:manageconnection'] = 'Administrar conexión de OpenID Connect'; +$string['ucp_general_intro'] = 'Aquí puede administrar su conexión a {$a}. Si está habilitado, podrá utilizar su cuenta de {$a} para iniciar sesión en Moodle en lugar de un nombre de usuario y contraseña separados. Una vez conectado, ya no tendrá que recordar el nombre de usuario y la contraseña para Moodle, todos los inicios de sesión serán gestionados por {$a}.'; +$string['ucp_login_start'] = 'Comenzar a usar {$a} para iniciar sesión en Moodle'; +$string['ucp_login_start_desc'] = 'Esto cambiará su cuenta para usar {$a} para iniciar sesión en Moodle. Una vez habilitado, deberá iniciar sesión con sus credenciales de {$a} - su nombre de usuario y contraseña actuales de Moodle no funcionarán. Puede desconectar su cuenta en cualquier momento y volver a iniciar sesión normalmente.'; +$string['ucp_login_stop'] = 'Dejar de usar {$a} para iniciar sesión en Moodle'; +$string['ucp_login_stop_desc'] = 'Actualmente, está utilizando {$a} para iniciar sesión en Moodle. Si hace clic en "Dejar de usar el inicio de sesión de {$a}" su cuenta de Moodle se desconectará de {$a}. Ya no podrá volver a iniciar sesión en Moodle con su cuenta de {$a}. Se le solicitará que cree un nombre de usuario y una contraseña, y a partir de allí podrá volver a iniciar sesión en Moodle directamente.'; +$string['ucp_login_status'] = 'El inicio de sesión de {$a} es:'; +$string['ucp_status_enabled'] = 'Habilitado'; +$string['ucp_status_disabled'] = 'Desactivado'; +$string['ucp_disconnect_title'] = 'Desconexión de {$a}'; +$string['ucp_disconnect_details'] = 'Esto desconectará su cuenta de Moodle de {$a}. Deberá crear un nombre de usuario y una contraseña para iniciar sesión en Moodle.'; +$string['ucp_title'] = 'Administración de {$a}'; + +// phpcs:enable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:enable moodle.Files.LangFilesOrdering.UnexpectedComment \ No newline at end of file diff --git a/auth/oidc/lang/fi/auth_oidc.php b/auth/oidc/lang/fi/auth_oidc.php new file mode 100644 index 00000000000..272309fcf51 --- /dev/null +++ b/auth/oidc/lang/fi/auth_oidc.php @@ -0,0 +1,133 @@ +. + +/** + * Finnish language strings. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +// phpcs:disable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:disable moodle.Files.LangFilesOrdering.UnexpectedComment + +$string['pluginname'] = 'OpenID Connect'; +$string['auth_oidcdescription'] = 'OpenID Connect -lisäosa mahdollistaa kertakirjautumisen käyttämällä määritettävissä olevaa identiteetintarjoajaa.'; +$string['cfg_authendpoint_key'] = 'Todennuksen päätepiste'; +$string['cfg_authendpoint_desc'] = 'Käytettävän identiteetintarjoajan todennuksen päätepisteen URI.'; +$string['cfg_autoappend_key'] = 'Lisää automaattisesti'; +$string['cfg_autoappend_desc'] = 'Lisää tämän merkkijonon automaattisesti, kun käyttäjät käyttävät kirjautumiseen käyttäjänimi/salasana-kulkua. Tästä on hyötyä, kun identiteetintarjoaja edellyttää yhtenäistä toimialuetta, mutta et halua, että käyttäjien on kirjoitettava toimialue jokaisen kirjautumisen yhteydessä. Jos käyttäjän täydellinen OpenID Connect -käyttäjätunnus on esimerkiksi james@example.com ja kirjoitat tähän kenttään @example.com, käyttäjän tarvitsee antaa käyttäjänimeksi vain james.
Huomautus: Jos käyttäjänimissä on ristiriitoja, esimerkiksi järjestelmässä on samanniminen Moodle-käyttäjä, prioriteettijärjestys määräytyy todennuslisäosan mukaan.'; +$string['cfg_clientid_key'] = 'Asiakastunnus'; +$string['cfg_clientid_desc'] = 'Identiteetintarjoajan palveluun rekisteröity asiakastunnus.'; +$string['cfg_clientsecret_key'] = 'Asiakassalaisuus'; +$string['cfg_clientsecret_desc'] = 'Identiteetintarjoajan palveluun rekisteröity asiakassalaisuus. Joidenkin palveluntarjoajien palveluissa tätä kutsutaan avaimeksi.'; +$string['cfg_err_invalidauthendpoint'] = 'Virheellinen todennuksen päätepiste'; +$string['cfg_err_invalidtokenendpoint'] = 'Virheellinen avaimen päätepiste'; +$string['cfg_err_invalidclientid'] = 'Virheellinen asiakastunnus'; +$string['cfg_err_invalidclientsecret'] = 'Virheellinen asiakassalaisuus'; +$string['cfg_icon_key'] = 'Kuvake'; +$string['cfg_icon_desc'] = 'Kirjautumissivulla palveluntarjoajan nimen vieressä näkyvä kuvake.'; +$string['cfg_iconalt_o365'] = 'Microsoft 365 -kuvake'; +$string['cfg_iconalt_locked'] = 'Lukittu-kuvake'; +$string['cfg_iconalt_lock'] = 'Lukkokuvake'; +$string['cfg_iconalt_go'] = 'Vihreä ympyrä'; +$string['cfg_iconalt_stop'] = 'Punainen ympyrä'; +$string['cfg_iconalt_user'] = 'Käyttäjäkuvake'; +$string['cfg_iconalt_user2'] = 'Vaihtoehtoinen käyttäjäkuvake'; +$string['cfg_iconalt_key'] = 'Avainkuvake'; +$string['cfg_iconalt_group'] = 'Ryhmäkuvake'; +$string['cfg_iconalt_group2'] = 'Vaihtoehtoinen ryhmäkuvake'; +$string['cfg_iconalt_mnet'] = 'MNET-kuvake'; +$string['cfg_iconalt_userlock'] = 'Käyttäjä ja lukko -kuvake'; +$string['cfg_iconalt_plus'] = 'Pluskuvake'; +$string['cfg_iconalt_check'] = 'Valintamerkki-kuvake'; +$string['cfg_iconalt_rightarrow'] = 'Oikealle osoittava nuolikuvake'; +$string['cfg_customicon_key'] = 'Mukautettu kuvake'; +$string['cfg_customicon_desc'] = 'Jos haluat käyttää mukautettua kuvaketta, lataa se tähän. Ladattu kuvake korvaa valittuna olevan kuvakkeen.

Mukautettujen kuvakkeiden käytössä huomioitavaa:
  • Kuvan kokoa ei muuteta kirjautumissivulla, joten suosittelemme lataamaan kuvan, jonka koko on enintään 35 x 35 pikseliä.
  • Jos olet ladannut mukautetun kuvan, mutta haluat palata käyttämään vakiokuvaketta, napsauta mukautetun kuvakkeen ruutua yllä ja valitse Poista ja sitten OK. Valitse lopuksi Tallenna muutokset tämän lomakkeen alaosassa. Valittu vakiokuvake näytetään tämän jälkeen Moodlen kirjautumissivulla.
'; +$string['cfg_debugmode_key'] = 'Kirjaa virheenkorjausviestit'; +$string['cfg_debugmode_desc'] = 'Jos asetus on käytössä, tiedot kirjataan Moodlen lokiin ongelmien tunnistamista varten.'; +$string['cfg_loginflow_key'] = 'Kirjautumiskulku'; +$string['cfg_loginflow_authcode'] = 'Valtuutuspyyntö'; +$string['cfg_loginflow_authcode_desc'] = 'Jos tämä kirjautumiskulku on käytössä, käyttäjä napsauttaa identiteetintarjoajan nimeä (ks. Palveluntarjoajan nimi) Moodlen kirjautumissivulla, jonka jälkeen käyttäjä ohjataan palveluntarjoajan sivulle kirjautumista varten. Jos kirjautuminen onnistuu, käyttäjä ohjataan takaisin Moodleen, jossa Moodle-kirjautuminen tapahtuu läpinäkyvästi. Tämä on standardisoitu ja turvallisin käyttäjien kirjautumismenetelmä.'; +$string['cfg_loginflow_rocreds'] = 'Käyttäjänimen/salasanan todennus'; +$string['cfg_loginflow_rocreds_desc'] = 'Jos tämä kirjautumiskulku on käytössä, käyttäjä kirjautuu Moodleen antamalla käyttäjänimen ja salasanan Moodlen kirjautumislomakkeeseen. Tunnistetiedot välitetään taustalla identiteetintarjoajalle todennusta varten. Tämä kulku on läpinäkyvin käyttäjän kannalta, koska käyttäjä ei ole suoraan tekemisissä identiteetintarjoajan kanssa. Huomaa, että kaikki identiteetintarjoajat eivät tue tätä kulkua.'; +$string['cfg_oidcresource_key'] = 'Resurssi'; +$string['cfg_oidcresource_desc'] = 'OpenID Connect -resurssi, jota lähetettävä pyyntö koskee.'; +$string['cfg_oidcscope_key'] = 'laajuus'; +$string['cfg_oidcscope_desc'] = 'Käytettävä OIDC-soveltamisala.'; +$string['cfg_opname_key'] = 'Palveluntarjoajan nimi'; +$string['cfg_opname_desc'] = 'Tämä on loppukäyttäjälle näkyvä selite, joka ilmoittaa kirjautumiseen käytettävien tunnistetietojen tyypin. Tätä palveluntarjoajan selitettä käytetään tämän lisäosan kaikissa käyttäjälle näkyvissä osioissa.'; +$string['cfg_redirecturi_key'] = 'Uudelleenohjauksen URI'; +$string['cfg_redirecturi_desc'] = 'Tämä on rekisteröitävä uudelleenohjauksen URI. OpenID Connect -identiteetintarjoaja pyytää tätä tietoa, kun rekisteröit Moodlen asiakkaaksi.
HUOMAUTUS: Anna URI OpenID Connect -palveluntarjoajan palveluun *täsmälleen* tässä näkyvässä muodossa. Muussa tapauksessa kirjautuminen OpenID Connect -palvelun avulla ei onnistu.'; +$string['cfg_tokenendpoint_key'] = 'Avaimen päätepiste'; +$string['cfg_tokenendpoint_desc'] = 'Käytettävän identiteetintarjoajan avaimen päätepiste.'; +$string['event_debug'] = 'Virheenkorjausviesti'; +$string['errorauthdisconnectemptypassword'] = 'Salasana ei voi olla tyhjä'; +$string['errorauthdisconnectemptyusername'] = 'Käyttäjänimi ei voi olla tyhjä'; +$string['errorauthdisconnectusernameexists'] = 'Annettu käyttäjänimi on jo käytössä. Valitse toinen nimi.'; +$string['errorauthdisconnectnewmethod'] = 'Käyttäjän kirjautumismenetelmä'; +$string['errorauthdisconnectinvalidmethod'] = 'Virheellinen kirjautumismenetelmä vastaanotettiin.'; +$string['errorauthdisconnectifmanual'] = 'Jos käytät manuaalista kirjautumismenetelmää, anna tunnistetiedot alla.'; +$string['errorauthinvalididtoken'] = 'Virheellinen id_token vastaanotettiin.'; +$string['errorauthloginfailednouser'] = 'Kirjautumisvirhe: käyttäjää ei löydy Moodlesta.'; +$string['errorauthnoauthcode'] = 'Todennuskoodia ei vastaanotettu.'; +$string['errorauthnocreds'] = 'Määritä OpenID Connect -asiakkaan tunnistetiedot.'; +$string['errorauthnoendpoints'] = 'Määritä OpenID Connect -palvelimen päätepisteet.'; +$string['errorauthnohttpclient'] = 'Määritä HTTP-asiakas.'; +$string['errorauthnoidtoken'] = 'OpenID Connectin id_token-avainta ei vastaanotettu.'; +$string['errorauthunknownstate'] = 'Tuntematon tila.'; +$string['errorauthuseralreadyconnected'] = 'Yhteys on jo muodostettu toiseen OpenID Connect -käyttäjään.'; +$string['errorauthuserconnectedtodifferent'] = 'Todennettu OpenID Connect -käyttäjä on jo yhdistetty Moodle-käyttäjään.'; +$string['errorbadloginflow'] = 'Määritetty kirjautumiskulku on virheellinen. Huomautus: Jos saat tämän viestin asennuksen tai päivityksen jälkeen, tyhjennä Moodlen välimuisti.'; +$string['errorjwtbadpayload'] = 'JWT-tietoja ei voitu lukea.'; +$string['errorjwtcouldnotreadheader'] = 'JWT-otsikkoa ei voitu lukea'; +$string['errorjwtempty'] = 'Vastaanotettu JWT on tyhjä, tai se ei ole kelvollinen merkkijono.'; +$string['errorjwtinvalidheader'] = 'Virheellinen JWT-otsikko'; +$string['errorjwtmalformed'] = 'Vastaanotettu JWT on virheellinen.'; +$string['errorjwtunsupportedalg'] = 'JWS Alg tai JWE ei ole tuettu'; +$string['erroroidcnotenabled'] = 'OpenID Connect -todennuslisäosa ei ole käytössä.'; +$string['errornodisconnectionauthmethod'] = 'Yhteyttä ei voi katkaista, koska vaihtoehtoista todennuslisäosaa ei ole määritetty (se voi olla käyttäjän edellinen kirjautumismenetelmä tai manuaalinen kirjautumismenetelmä).'; +$string['erroroidcclientinvalidendpoint'] = 'Virheellinen pääpisteen URI vastaanotettiin.'; +$string['erroroidcclientnocreds'] = 'Määritä asiakkaan tunnistetiedot setcreds-komennolla'; +$string['erroroidcclientnoauthendpoint'] = 'Todennuksen päätepistettä ei ole määritetty. Määritä komennolla $this->setendpoints'; +$string['erroroidcclientnotokenendpoint'] = 'Avaimen päätepistettä ei ole määritetty. Määritä komennolla $this->setendpoints'; +$string['erroroidcclientinsecuretokenendpoint'] = 'Avaimen päätepisteen on käytettävä tähän SSL/TLS-yhteyttä.'; +$string['errorucpinvalidaction'] = 'Virheellinen toiminto vastaanotettiin.'; +$string['erroroidccall'] = 'OpenID Connect -palvelussa tapahtui virhe. Lisätietoja on lokeissa.'; +$string['erroroidccall_message'] = 'Virhe OpenID Connect -palvelussa: {$a}'; +$string['eventuserauthed'] = 'Käyttäjä valtuutettiin OpenID Connectin avulla'; +$string['eventusercreated'] = 'Käyttäjä luotiin OpenID Connectin avulla'; +$string['eventuserconnected'] = 'Käyttäjä yhdistettiin OpenID Connectin avulla'; +$string['eventuserloggedin'] = 'Käyttäjä kirjautui OpenID Connectin avulla'; +$string['eventuserdisconnected'] = 'Käyttäjän OpenID Connect -yhteys katkaistiin'; +$string['oidc:manageconnection'] = 'OpenID Connect -yhteyden hallinta'; +$string['ucp_general_intro'] = 'Tässä kohdassa voit hallita {$a} -yhteyttä. Jos asetus on käytössä, voit kirjautua Moodleen käyttämällä {$a} -tiliäsi erillisen käyttäjänimen ja salasanan sijaan. Kun yhteys on luotu, sinun ei tarvitse muistaa Moodle-käyttäjänimeä ja -salasanaa, koska {$a} huolehtii kirjautumisesta.'; +$string['ucp_login_start'] = 'Aloita palvelun {$a} käyttö Moodle-kirjautumiseen'; +$string['ucp_login_start_desc'] = 'Voit käyttää {$a} -tiliäsi Moodle-kirjautumiseen. Kun asetus on käytössä, kirjaudut Moodleen käyttämällä {$a} -tunnistetietojasi. Nykyinen Moodle-käyttäjänimi ja -salasana eivät enää toimi. Voit katkaista tilisi yhteyden milloin tahansa ja palata käyttämään normaalia kirjautumista.'; +$string['ucp_login_stop'] = 'Lopeta palvelun {$a} käyttö Moodle-kirjautumiseen'; +$string['ucp_login_stop_desc'] = '{$a} on tällä hetkellä käytössä Moodle-kirjautumiseen. Jos valitset Lopeta palvelun {$a} käyttö -asetuksen, Moodle-tilin yhteys palveluun {$a} katkaistaan. Tämän jälkeen et voi kirjautua Moodleen käyttämällä {$a} -tiliäsi. Sinua pyydetään luomaan käyttäjänimi ja salasana, joiden avulla voit kirjautua suoraan Moodleen.'; +$string['ucp_login_status'] = '{$a} -kirjautuminen:'; +$string['ucp_status_enabled'] = 'Käytössä'; +$string['ucp_status_disabled'] = 'Ei käytössä'; +$string['ucp_disconnect_title'] = '{$a} -yhteyden katkaisu'; +$string['ucp_disconnect_details'] = 'Tämä katkaisee Moodle-tilin yhteyden kohteesta {$a}. Tarvitset käyttäjänimen ja salasanan, jotta voit kirjautua Moodleen.'; +$string['ucp_title'] = '{$a} -hallinta'; + +// phpcs:enable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:enable moodle.Files.LangFilesOrdering.UnexpectedComment \ No newline at end of file diff --git a/auth/oidc/lang/fr/auth_oidc.php b/auth/oidc/lang/fr/auth_oidc.php new file mode 100644 index 00000000000..885f66c9162 --- /dev/null +++ b/auth/oidc/lang/fr/auth_oidc.php @@ -0,0 +1,169 @@ +. + +/** + * French language strings. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +// phpcs:disable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:disable moodle.Files.LangFilesOrdering.UnexpectedComment + +$string['pluginname'] = 'OpenID Connect'; +$string['auth_oidcdescription'] = 'Le plug-in OpenID Connect fournit une fonctionnalité SSO avec des fournisseurs d\'identité configurables.'; + +$string['cfg_authendpoint_key'] = 'Point d\'accès d\'autorisation'; +$string['cfg_authendpoint_desc'] = 'URI du point d\'accès d\'autorisation de votre fournisseur d\'identité à utiliser.'; +$string['cfg_autoappend_key'] = 'Ajout automatique'; +$string['cfg_autoappend_desc'] = 'Ajoutez automatiquement cette chaîne lors de la connexion d\'utilisateurs utilisant la méthode d\'authentification "Nom d\'utilisateur/mot de passe". Cette opération est utile lorsque votre fournisseur d\'identité nécessite un domaine courant, mais ne souhaite pas exiger aux utilisateurs de le saisir lors de la connexion. Par exemple, si l\'utilisateur OpenID Connect complet est « james@exemple.com » et que vous saisissez « @exemple.com » ici, l\'utilisateur n\'a qu\'à saisir « james » comme nom d\'utilisateur.
Remarque : en cas de conflit entre les noms d\'utilisateur, par exemple s\'il existe un utilisateur Moodle du même nom, la priorité du plug-in d\'authentification permet de déterminer l\'utilisateur qui l\'emporte.'; +$string['cfg_clientid_key'] = 'ID client'; +$string['cfg_clientid_desc'] = 'Votre ID client enregistré sur le fournisseur d\'identité.'; +$string['cfg_clientsecret_key'] = 'Secret client'; +$string['cfg_clientsecret_desc'] = 'Votre secret client enregistré sur le fournisseur d\'identité. Sur certains fournisseurs, il est également appelé clé.'; +$string['cfg_domainhint_key'] = 'Domaine de l\'utilisateur'; +$string['cfg_domainhint_desc'] = 'Lorsque la méthode d\'authentification "Demande d\'autorisation" est utilisée, passez cette valeur pour le paramètre "domain_hint". "domain_hint" est utilisé par certains fournisseurs OpenID Connect pour rendre le processus de connection plus simple pour les utilisateurs. Vérifiez avec votre fournisseur s\'il supporte ce paramètre.'; +$string['cfg_err_invalidauthendpoint'] = 'Point d\'accès d\'autorisation non valide'; +$string['cfg_err_invalidtokenendpoint'] = 'Point d\'accès de jeton non valide'; +$string['cfg_err_invalidclientid'] = 'ID client non valide'; +$string['cfg_err_invalidclientsecret'] = 'Secret client non valide'; +$string['cfg_icon_key'] = 'Icône'; +$string['cfg_icon_desc'] = 'Icône à afficher près du nom de fournisseur sur la page de connexion.'; +$string['cfg_iconalt_o365'] = 'Icône Microsoft 365'; +$string['cfg_iconalt_locked'] = 'Icône verrouillée'; +$string['cfg_iconalt_lock'] = 'Icône de verrouillage'; +$string['cfg_iconalt_go'] = 'Cercle vert'; +$string['cfg_iconalt_stop'] = 'Cercle rouge'; +$string['cfg_iconalt_user'] = 'Icône utilisateur'; +$string['cfg_iconalt_user2'] = 'Autre icône utilisateur'; +$string['cfg_iconalt_key'] = 'Icône de clé'; +$string['cfg_iconalt_group'] = 'Icône de groupe'; +$string['cfg_iconalt_group2'] = 'Autre icône de groupe'; +$string['cfg_iconalt_mnet'] = 'Icône MNET'; +$string['cfg_iconalt_userlock'] = 'Utilisateur avec icône de verrouillage'; +$string['cfg_iconalt_plus'] = 'Icône Plus'; +$string['cfg_iconalt_check'] = 'Icône de coche'; +$string['cfg_iconalt_rightarrow'] = 'Icône de flèche pointant vers la droite'; +$string['cfg_customicon_key'] = 'Icône personnalisée'; +$string['cfg_customicon_desc'] = 'Si vous souhaitez utiliser votre propre icône, téléchargez-la ici. Cette opération remplace toute icône choisie ci-dessus.

Remarques sur l\'utilisation des icônes personnalisées :
  • cette image ne sera pas redimensionnée sur la page de connexion. Nous recommandons donc de télécharger une image de 35x35 pixels maximum.
  • Si vous avez téléchargé une icône personnalisée et que vous souhaitez revenir à l\'une des icônes de stockage, cliquez sur l\'icône personnalisée dans la zone ci-dessus, puis cliquez sur « Supprimer », puis sur « OK », puis cliquez sur « Enregistrer des modifications » en bas de ce formulaire. L\'icône de stockage sélectionnée apparaît maintenant sur la page de connexion Moodle.
'; +$string['cfg_debugmode_key'] = 'Enregistrer les messages de débogage'; +$string['cfg_debugmode_desc'] = 'Si ce réglage est activé, les informations seront enregistrées dans le journal Moodle, cela peut aider à identifier les problèmes.'; +$string['cfg_loginflow_key'] = 'Méthode d\'authentification'; +$string['cfg_loginflow_authcode'] = 'Demande d\'autorisation (recommandée)'; +$string['cfg_loginflow_authcode_desc'] = 'À l\'aide de cette méthode, l\'utilisateur clique sur le fournisseur d\'identité (voir « Nom du fournisseur » ci-dessus) sur la page de connexion Moodle et est redirigé vers le fournisseur pour se connecter. Une fois la connexion réussie, l\'utilisateur est redirigé vers Moodle où la connexion Moodle est effectuée en toute transparence. Il s\'agit pour l\'utilisateur du moyen le plus sécurisé et standardisé pour se connecter.'; +$string['cfg_loginflow_rocreds'] = 'Authentification via nom d\'utilisateur/mot de passe'; +$string['cfg_loginflow_rocreds_desc'] = 'À l\'aide de cette méthode, l\'utilisateur saisit son nom d\'utilisateur et son mot de passe dans le formulaire de connexion Moodle comme il le ferait avec une connexion manuelle. Ses informations d\'identification sont ensuite transmises au fournisseur d\'identité en arrière-plan pour obtenir son authentification. Cette méthode est la plus transparente pour l\'utilisateur car il n\'a aucune interaction directe avec le fournisseur d\'identité. Notez que l\'ensemble des fournisseurs d\'identité prennent en charge ce flux.'; +$string['cfg_oidcresource_key'] = 'Ressource'; +$string['cfg_oidcresource_desc'] = 'Ressource OpenID Connect pour laquelle envoyer la demande.'; +$string['cfg_oidcscope_key'] = 'Porté'; +$string['cfg_oidcscope_desc'] = 'L\'étendue OIDC à utiliser.'; +$string['cfg_opname_key'] = 'Nom du fournisseur'; +$string['cfg_opname_desc'] = 'Il s\'agit d\'une étiquette destinée à l\'utilisateur final qui identifie le type d\'informations d\'identification dont l\'utilisateur doit se servir pour se connecter. Cette étiquette est utilisée sur toutes les sections permettant d\'identifier votre fournisseur et qui sont visibles par l\'utilisateur.'; +$string['cfg_redirecturi_key'] = 'URI de redirection'; +$string['cfg_redirecturi_desc'] = 'URI à enregistrer comme « URI de redirection ». Votre fournisseur d\'identité OpenID Connect doit demander cet URI lors de l\'enregistrement de Moodle comme client.
REMARQUE : vous devez entrer cet URI dans votre fournisseur OpenID Connect *exactement* tel qu\'il apparaît ici. La moindre différence empêchera les connexions d\'utiliser OpenID Connect.'; +$string['cfg_tokenendpoint_key'] = 'Point d\'accès de jeton'; +$string['cfg_tokenendpoint_desc'] = 'URI du point d\'accès de jeton de votre fournisseur d\'identité à utiliser.'; +$string['cfg_userrestrictions_key'] = 'Restrictions utilisateur'; +$string['cfg_userrestrictions_desc'] = 'Ne permettre qu\'aux utilisateurs correspondant à certains critères de se connecter.
Comment utiliser les restrictions d\'utilisateurs :
  • Entrez une expression régulière correspondant aux noms d\'utilisateurs des utilisateurs que vous souhaitez autoriser.
  • Entrez une expression par ligne
  • Si vous entrez plusieurs expressions, l\'utilisateur sera autorisé s\'il correspond à N\'IMPORTE LAQUELLE des expressions.
  • Le caractère "/" doit être précédé de "\".
  • Si vous n\'entrez aucune restriction ci-dessus, tous les utilisateurs pouvant se connecter au fournisseur OpenID Connect seront acceptés par Moodle.
  • Tout utilisateur ne correspondant à aucune des expressions entrées ne pourra pas se connecter à l\'aide d\'OpenID Connect.
'; +$string['event_debug'] = 'Message de débogage'; + +$string['errorauthdisconnectemptypassword'] = 'Le mot de passe ne peut pas être vide'; +$string['errorauthdisconnectemptyusername'] = 'Le nom d\'utilisateur ne peut pas être vide'; +$string['errorauthdisconnectusernameexists'] = 'Ce nom d\'utilisateur est déjà attribué. Choisissez-en un autre.'; +$string['errorauthdisconnectnewmethod'] = 'Utiliser une méthode de connexion'; +$string['errorauthdisconnectinvalidmethod'] = 'Méthode de connexion non valide reçue.'; +$string['errorauthdisconnectifmanual'] = 'Si vous utilisez la méthode de connexion manuelle, saisissez les informations d\'identification ci-dessous.'; +$string['errorauthgeneral'] = 'Il y a eu un problème lors de votre connexion. Veuillez contacter votre administrateur pour de l\'aide.'; +$string['errorauthinvalididtoken'] = 'id_token reçu non valide.'; +$string['errorauthloginfailednouser'] = 'Connexion non valide : utilisateur introuvable dans Moodle. Si le paramètre "authpreventaccountcreation" est activé sur ce site, cela peut signifier que vous avez besoin d\'un administrateur pour vous créer un compte.'; +$string['errorauthnoauthcode'] = 'Code d\'authentification non reçu. Les journaux d\'erreurs peuvent contenir plus d\'informations.'; +$string['errorauthnocreds'] = 'Configurez les informations d\'identification du client OpenID Connect.'; +$string['errorauthnoendpoints'] = 'Configurez les points d\'accès du serveur OpenID Connect.'; +$string['errorauthnohttpclient'] = 'Définissez un client HTTP.'; +$string['errorauthnoidtoken'] = 'Jeton OpenID Connect id_token non reçu.'; +$string['errorauthunknownstate'] = 'État inconnu.'; +$string['errorauthuseralreadyconnected'] = 'Vous êtes déjà connecté à un autre utilisateur OpenID Connect.'; +$string['errorauthuserconnectedtodifferent'] = 'L\'utilisateur OpenID Connect qui s\'est authentifié est déjà connecté à un utilisateur Moodle.'; +$string['errorbadloginflow'] = 'Méthode d\'authentification spécifiée non valide. Remarque : si vous recevez ce message après une installation ou une mise à niveau récente, effacez votre cache Moodle.'; +$string['errorjwtbadpayload'] = 'Impossible de lire la charge JWT.'; +$string['errorjwtcouldnotreadheader'] = 'Impossible de lire l\'en-tête JWT'; +$string['errorjwtempty'] = 'JWT vide ou sans chaîne reçu.'; +$string['errorjwtinvalidheader'] = 'En-tête JWT non valide'; +$string['errorjwtmalformed'] = 'JWT malformé reçu.'; +$string['errorjwtunsupportedalg'] = 'JWS Alg ou JWE non pris en charge'; +$string['errorlogintoconnectedaccount'] = 'Cet utilisateur Microsoft 365 est associé à un utilisateur Moodle, mais la connexion via OpenID Connect n\'est pas activée pour cet utilisateur Moodle. Veuillez vous connecter à Moodle en utilisant la méthode d\'authentification définie dans le compte de l\'utilisateur afin d\'utiliser les fonctionnalités Microsoft 365'; +$string['erroroidcnotenabled'] = 'Le plug-in d\'authentification OpenID Connect n\'est pas activé.'; +$string['errornodisconnectionauthmethod'] = 'Déconnexion impossible en l\'absence de plug-in d\'autorisation activé vers lequel se rabattre (soit la méthode de connexion précédente de l\'utilisateur, soit la méthode de connexion manuelle).'; +$string['erroroidcclientinvalidendpoint'] = 'URI du point d\'accès non valide reçu.'; +$string['erroroidcclientnocreds'] = 'Définissez les informations d\'identification client avec setcreds'; +$string['erroroidcclientnoauthendpoint'] = 'Aucun point d\'accès d\'autorisation défini. Définissez-le avec $this->setendpoints'; +$string['erroroidcclientnotokenendpoint'] = 'Aucun point d\'accès de jeton défini. Définissez-le avec $this->setendpoints'; +$string['erroroidcclientinsecuretokenendpoint'] = 'Le point d\'accès de jeton doit utiliser SSL/TLS.'; +$string['errorrestricted'] = 'Ce site impose des restrictions quant aux utilisateurs pouvant se connecter avec OpenID Connect. Ces restrictions vous empêchent actuellement de vous connecter.'; +$string['errorucpinvalidaction'] = 'Action non valide reçue.'; +$string['erroroidccall'] = 'Erreur dans OpenID Connect. Pour plus d\'informations, consultez les journaux d\'erreurs.'; +$string['erroroidccall_message'] = 'Erreur dans OpenID Connect : {$a}'; +$string['errorinvalidredirect_message'] = 'L\'URL à laquelle vous tentez de vous rediriger n\'existe pas.'; + +$string['eventuserauthed'] = 'Utilisateur autorisé avec OpenID Connect'; +$string['eventusercreated'] = 'Utilisateur créé avec OpenID Connect'; +$string['eventuserconnected'] = 'Utilisateur connecté à OpenID Connect'; +$string['eventuserloggedin'] = 'Utilisateur identifié avec OpenID Connect'; +$string['eventuserdisconnected'] = 'Utilisateur déconnecté d\'OpenID Connect'; + +$string['oidc:manageconnection'] = 'Permettre la connexion et la déconnexion OpenID'; +$string['oidc:manageconnectionconnect'] = 'Permettre la connexion OpenID'; +$string['oidc:manageconnectiondisconnect'] = 'Permettre la déconnexion OpenID'; + +$string['privacy:metadata:auth_oidc'] = 'Authentification OpenID Connect'; +$string['privacy:metadata:auth_oidc_prevlogin'] = 'Méthodes de connexion précédentes pour annuler les connexions Microsoft 365'; +$string['privacy:metadata:auth_oidc_prevlogin:userid'] = 'L\'identifiant de l\'utilisateur Moodle'; +$string['privacy:metadata:auth_oidc_prevlogin:method'] = 'La méthode de connexion précédente'; +$string['privacy:metadata:auth_oidc_prevlogin:password'] = 'Le mot de passe précédent de l\'utilisateur (chiffré)'; +$string['privacy:metadata:auth_oidc_token'] = 'Jetons OpenID Connect'; +$string['privacy:metadata:auth_oidc_token:oidcuniqid'] = 'L\'identifiant utilisateur unique de OIDCs'; +$string['privacy:metadata:auth_oidc_token:username'] = 'Le nom d\'utilisateur de l\'utilisateur Moodle'; +$string['privacy:metadata:auth_oidc_token:userid'] = 'Le ID de l\'utilisateur Moodle'; +$string['privacy:metadata:auth_oidc_token:oidcusername'] = 'Le nom d\'utilisateur de l\'utilisateur OIDC'; +$string['privacy:metadata:auth_oidc_token:scope'] = 'La portée du jeton'; +$string['privacy:metadata:auth_oidc_token:tokenresource'] = 'La ressource du jeton'; +$string['privacy:metadata:auth_oidc_token:authcode'] = 'Le code d\'authentification du jeton'; +$string['privacy:metadata:auth_oidc_token:token'] = 'Le jeton'; +$string['privacy:metadata:auth_oidc_token:expiry'] = 'L\'expiration du jeton'; +$string['privacy:metadata:auth_oidc_token:refreshtoken'] = 'Le jeton de rafraîchissement'; +$string['privacy:metadata:auth_oidc_token:idtoken'] = 'Le jeton ID'; + +// Dans les chaînes suivantes, $a réfère à un nom personnalisable pour le gestionnaire d'identité. Par exemple, ce pourrait être +// "Microsoft 365", "OpenID Connect", etc. +$string['ucp_general_intro'] = 'Vous pouvez gérer votre connexion à {$a} ici. Si ce réglage est activé, vous pourrez voir votre compte {$a} pour vous connecter à Moodle au lieu d\'un nom d\'utilisateur et d\'un mot de passe distincts. Une fois connecté, vous n\'aurez plus à mémoriser votre nom d\'utilisateur et votre mot de passe pour Moodle ; toutes les connexions seront gérées par {$a}.'; +$string['ucp_login_start'] = 'Commencer à utiliser {$a} pour se connecter à Moodle'; +$string['ucp_login_start_desc'] = 'Votre compte passera à {$a} pour la connexion à Moodle. Une fois ce réglage activé, vous vous connecterez à l\'aide de vos informations d\'identification {$a} (votre mot de passe et votre nom d\'utilisateur Moodle actuels ne fonctionneront pas). Vous pouvez vous déconnecter de votre compte à tout moment et utiliser de nouveau la méthode d\'authentification habituelle Moodle.'; +$string['ucp_login_stop'] = 'Cesser d\'utiliser {$a} pour se connecter à Moodle'; +$string['ucp_login_stop_desc'] = 'Vous utilisez actuellement {$a} pour vous connecter à Moodle. Cliquez sur « Cesser d\'utiliser la connexion {$a} » pour déconnecter votre compte Moodle de {$a}. Vous ne pourrez plus vous connecter à Moodle avec votre compte {$a}. Vous serez invité à créer un nom d\'utilisateur et un mot de passe, et vous pourrez ensuite vous connecter directement à Moodle.'; +$string['ucp_login_status'] = 'Connexion {$a} :'; +$string['ucp_status_enabled'] = 'Activé'; +$string['ucp_status_disabled'] = 'Désactivé'; +$string['ucp_disconnect_title'] = 'Déconnexion {$a}'; +$string['ucp_disconnect_details'] = 'Cette opération déconnectera votre compte Moodle de {$a}. Vous aurez besoin de créer un nom d\'utilisateur et un mot de passe pour vous connecter à Moodle.'; +$string['ucp_title'] = 'Gestion de {$a}'; +$string['ucp_o365accountconnected'] = 'Ce compte Microsoft 365 est déjà associé à un autre compte Moodle.'; + +// phpcs:enable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:enable moodle.Files.LangFilesOrdering.UnexpectedComment \ No newline at end of file diff --git a/auth/oidc/lang/it/auth_oidc.php b/auth/oidc/lang/it/auth_oidc.php new file mode 100644 index 00000000000..eed12857267 --- /dev/null +++ b/auth/oidc/lang/it/auth_oidc.php @@ -0,0 +1,133 @@ +. + +/** + * Italian language strings. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +// phpcs:disable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:disable moodle.Files.LangFilesOrdering.UnexpectedComment + +$string['pluginname'] = 'OpenID Connect'; +$string['auth_oidcdescription'] = 'Il plugin OpenID Connect fornisce funzionalità single-sign-on utilizzando Identity Provider configurabili.'; +$string['cfg_authendpoint_key'] = 'Endpoint di autorizzazione'; +$string['cfg_authendpoint_desc'] = 'L\'URI dell\'endpoint di autorizzazione dall\'Identity Provider da utilizzare.'; +$string['cfg_autoappend_key'] = 'Aggiungi automaticamente'; +$string['cfg_autoappend_desc'] = 'Aggiunge automaticamente questa stringa durante il login di utenti utilizzando il flusso di login Nome utente/Password. È utile quando l\'Identity Provider richiede un dominio comune senza però richiederne l\'inserimento durante il login. Ad esempio, se l\'utente OpenID Connect completo è "james@example.com" e si inserisce "@example.com", l\'utente deve inserire solo "james" come nome utente.
Nota: in caso di conflitto tra nomi utente, ovvero quando esiste un utente Moodle con lo stesso nome, la priorità del plugin di autenticazione è utilizzata per determinare quale utente ha la meglio.'; +$string['cfg_clientid_key'] = 'ID cliente'; +$string['cfg_clientid_desc'] = 'Il tuo ID cliente registrato sull\'Identity Provider.'; +$string['cfg_clientsecret_key'] = 'Segreto cliente'; +$string['cfg_clientsecret_desc'] = 'Il tuo segreto cliente registrato sull\'Identity Provider. Su alcuni provider, viene anche indicato come chiave.'; +$string['cfg_err_invalidauthendpoint'] = 'Endpoint di autorizzazione non valido'; +$string['cfg_err_invalidtokenendpoint'] = 'Endpoint token non valido'; +$string['cfg_err_invalidclientid'] = 'ID cliente non valido'; +$string['cfg_err_invalidclientsecret'] = 'Segreto cliente non valido'; +$string['cfg_icon_key'] = 'Icona'; +$string['cfg_icon_desc'] = 'Un\'icona visualizzata accanto al nome del provider sulla pagina di login.'; +$string['cfg_iconalt_o365'] = 'Icona Microsoft 365'; +$string['cfg_iconalt_locked'] = 'Icona Bloccato'; +$string['cfg_iconalt_lock'] = 'Icona blocca'; +$string['cfg_iconalt_go'] = 'Cerchio verde'; +$string['cfg_iconalt_stop'] = 'Cerchio rosso'; +$string['cfg_iconalt_user'] = 'Icona utente'; +$string['cfg_iconalt_user2'] = 'Icona utente alternativa'; +$string['cfg_iconalt_key'] = 'Icona chiave'; +$string['cfg_iconalt_group'] = 'Icona gruppo'; +$string['cfg_iconalt_group2'] = 'Icona gruppo alternativa'; +$string['cfg_iconalt_mnet'] = 'Icona MNET'; +$string['cfg_iconalt_userlock'] = 'Utente con icona blocco'; +$string['cfg_iconalt_plus'] = 'Icona più'; +$string['cfg_iconalt_check'] = 'Icona segno di spunta'; +$string['cfg_iconalt_rightarrow'] = 'Icona freccia verso destra'; +$string['cfg_customicon_key'] = 'Icona personalizzato'; +$string['cfg_customicon_desc'] = 'Se desideri utilizzare la tua icona, caricala qui. Questa ha la precedenza su qualsiasi icona scelta in precedenza.

Note sull\'utilizzo di icone personalizzate:
  • Questa immagine non verrà ridimensionata sulla pagina di login, pertanto si consiglia di caricare un\'immagine di dimensioni inferiori a 35x35 pixel.
  • Se hai caricato un\'icona personalizzata e desideri tornare a una delle icone di origine, fai clic sull\'icona personalizzata nella casella precedente, scegli "Elimina", fai clic su "OK", quindi su "Salva modifiche" nella parte inferiore di questo modulo. L\'icona di origine selezionata verrà visualizzata nella pagina di login di Moodle.
'; +$string['cfg_debugmode_key'] = 'Registra messaggi di debug'; +$string['cfg_debugmode_desc'] = 'Abilitando questa opzione, le informazioni verranno registrate nel log di Moodle per risolvere problemi di identificazione.'; +$string['cfg_loginflow_key'] = 'Flusso di login'; +$string['cfg_loginflow_authcode'] = 'Richiesta di autorizzazione'; +$string['cfg_loginflow_authcode_desc'] = 'Utilizzando questo flusso, l\'utente fa clic sul nome dell\'Identity Provider (vedere "Nome provider" in precedenza) nella pagina login di Moodle e viene reindirizzato al provider per il login. Dopo il login, l\'utente viene nuovamente reindirizzato in Moodle dove il login a Moodle viene eseguito in maniera trasparente. Questo è il metodo di login più standardizzato e sicuro.'; +$string['cfg_loginflow_rocreds'] = 'Autenticazione nome utente/password'; +$string['cfg_loginflow_rocreds_desc'] = 'Utilizzando questo flusso, l\'utente inserisce il nome utente e la password nel modulo di login di Moodle seguendo la stessa procedura del login manuale. Le credenziali vengono quindi passate all\'Identity Provider in background per ottenere l\'autenticazione. Questo flusso è quello più trasparente per l\'utente in quanto non esiste interazione diretta con l\'Identity Provider. Osservare che non tutti gli Identity Provider supportano questo flusso.'; +$string['cfg_oidcresource_key'] = 'Risorsa'; +$string['cfg_oidcresource_desc'] = 'La risorsa OpenID Connect per la quale inviare la richiesta.'; +$string['cfg_oidcscope_key'] = 'Scopo'; +$string['cfg_oidcscope_desc'] = 'L\'ambito OIDC da utilizzare.'; +$string['cfg_opname_key'] = 'Nome del provider'; +$string['cfg_opname_desc'] = 'Si tratta di un\'etichetta presentata all\'utente finale che identifica il tipo di credenziali che l\'utente deve utilizzare per eseguire il login. Questa etichetta viene utilizzata in tutta la parte rivolta all\'utente del plugin per identificare il provider.'; +$string['cfg_redirecturi_key'] = 'URI di reindirizzamento'; +$string['cfg_redirecturi_desc'] = 'Si tratta dell\'URI da registrare come "URI di reindirizzamento". L\'Identity Provider di OpenID Connect deve richiedere questa informazione durante la registrazione come client.
NOTA: devi immettere questa informazione nell\'apposito campo *esattamente* come appare qui. Qualsiasi differenza non consentirà di accedere con OpenID Connect.'; +$string['cfg_tokenendpoint_key'] = 'Endpoint token'; +$string['cfg_tokenendpoint_desc'] = 'L\'URI dell\'endpoint token dall\'Identity Provider.'; +$string['event_debug'] = 'Messaggio di debug'; +$string['errorauthdisconnectemptypassword'] = 'La password non può essere vuota'; +$string['errorauthdisconnectemptyusername'] = 'Il nome utente non può essere vuoto'; +$string['errorauthdisconnectusernameexists'] = 'Questo nome utente è già impegnato. Sceglierne uno diverso.'; +$string['errorauthdisconnectnewmethod'] = 'Utilizza metodo di login'; +$string['errorauthdisconnectinvalidmethod'] = 'Ricevuto metodo di login non valido.'; +$string['errorauthdisconnectifmanual'] = 'Se si utilizza il metodo di login manuale, immettere le credenziali sottostanti.'; +$string['errorauthinvalididtoken'] = 'id_token non valido ricevuto.'; +$string['errorauthloginfailednouser'] = 'Login non valido: utente non trovato in Moodle.'; +$string['errorauthnoauthcode'] = 'Codice di autenticazione non ricevuto.'; +$string['errorauthnocreds'] = 'Configurare le credenziali cliente OpenID Connect.'; +$string['errorauthnoendpoints'] = 'Configurare gli endpoint server OpenID Connect.'; +$string['errorauthnohttpclient'] = 'Impostare un client HTTP.'; +$string['errorauthnoidtoken'] = 'OpenID Connect id_token non ricevuto.'; +$string['errorauthunknownstate'] = 'Stato sconosciuto.'; +$string['errorauthuseralreadyconnected'] = 'Sei già connesso a un utente OpenID Connect diverso.'; +$string['errorauthuserconnectedtodifferent'] = 'L\'utente OpenID Connect autenticato è già connesso a un utente Moodle.'; +$string['errorbadloginflow'] = 'Specificato flusso di login non valido. Nota: se stai ricevendo questo messaggio dopo un\'installazione o aggiornamento recente, cancella la cache Moodle.'; +$string['errorjwtbadpayload'] = 'Impossibile leggere payload JWT.'; +$string['errorjwtcouldnotreadheader'] = 'Impossibile leggere intestazione JWT'; +$string['errorjwtempty'] = 'Ricevuto JWT vuoto o di tipo non stringa.'; +$string['errorjwtinvalidheader'] = 'Intestazione JWT non valida'; +$string['errorjwtmalformed'] = 'Ricevuto JWT danneggiato.'; +$string['errorjwtunsupportedalg'] = 'JWS Alg o JWE non supportato'; +$string['erroroidcnotenabled'] = 'Il plugin di autenticazione OpenID Connect non è abilitato.'; +$string['errornodisconnectionauthmethod'] = 'Impossibile disconnettersi perché non esiste un plugin di autenticazione abilitato verso cui eseguire il fallback (metodo di accesso precedente dell\'utente o metodo di accesso manuale).'; +$string['erroroidcclientinvalidendpoint'] = 'Ricevuto URI endpoint non valido.'; +$string['erroroidcclientnocreds'] = 'Impostare credenziali cliente con segreti'; +$string['erroroidcclientnoauthendpoint'] = 'Nessun endpoint di autorizzazione impostato. Impostarlo con $this->setendpoints'; +$string['erroroidcclientnotokenendpoint'] = 'Nessun endpoint token impostato. Impostare con $this->setendpoints'; +$string['erroroidcclientinsecuretokenendpoint'] = 'L\'endpoint token deve utilizzare SSL/TLS.'; +$string['errorucpinvalidaction'] = 'Ricevuta azione non valida.'; +$string['erroroidccall'] = 'Si è verificato un errore in OpenID Connect. Consultare i log per ulteriori informazioni.'; +$string['erroroidccall_message'] = 'Si è verificato un errore in OpenID Connect: {$a}'; +$string['eventuserauthed'] = 'Utente autorizzato con OpenID Connect'; +$string['eventusercreated'] = 'Utente creato con OpenID Connect'; +$string['eventuserconnected'] = 'Utente connesso a OpenID Connect'; +$string['eventuserloggedin'] = 'Utente autenticato con OpenID Connect'; +$string['eventuserdisconnected'] = 'Utente disconnesso da OpenID Connect'; +$string['oidc:manageconnection'] = 'Gestisci connessione OpenID Connect'; +$string['ucp_general_intro'] = 'Qui puoi gestire la connessione a {$a}. Abilitando questa opzione, sarai in grado di utilizzare il tuo account {$a} per accedere a Moodle anziché un nome utente e una password separati. Dopo la connessione, non dovrai più ricordare il nome utente e la password per Moodle, tutti i login verranno gestiti da {$a}.'; +$string['ucp_login_start'] = 'Inizia a utilizzare {$a} per accedere a Moodle'; +$string['ucp_login_start_desc'] = 'Il tuo account verrà cambiato per utilizzare {$a} per accedere a Moodle. Dopo che è stato abilitato, l\'accesso verrà eseguito utilizzando le tue credenziali {$a} - il nome utente e la password Moodle correnti non funzioneranno. In qualsiasi momento puoi disconnetterti dal tuo account e tornare a eseguire il login normalmente.'; +$string['ucp_login_stop'] = 'Non utilizzare più {$a} per accedere a Moodle'; +$string['ucp_login_stop_desc'] = 'Stai attualmente utilizzando {$a} per accedere a Moodle. Facendo clic su "Non utilizzare più il login {$a}", l\'account Moodle verrà disconnesso da {$a}. Non potrai più accedere a Moodle con il tuo account {$a}. Ti verrà chiesto di creare un nome utente e una password con cui potrai accedere a Moodle direttamente.'; +$string['ucp_login_status'] = 'Login {$a} è:'; +$string['ucp_status_enabled'] = 'Abilitato'; +$string['ucp_status_disabled'] = 'Disabilitato'; +$string['ucp_disconnect_title'] = 'Disconnessione {$a}'; +$string['ucp_disconnect_details'] = 'Il tuo account Moodle verrà disconnesso da {$a}. Per accedere a Moodle dovrai creare un nome utente e una password.'; +$string['ucp_title'] = 'Gestione {$a}'; + +// phpcs:enable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:enable moodle.Files.LangFilesOrdering.UnexpectedComment \ No newline at end of file diff --git a/auth/oidc/lang/ja/auth_oidc.php b/auth/oidc/lang/ja/auth_oidc.php new file mode 100644 index 00000000000..12adba7491e --- /dev/null +++ b/auth/oidc/lang/ja/auth_oidc.php @@ -0,0 +1,133 @@ +. + +/** + * Japanese language strings. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +// phpcs:disable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:disable moodle.Files.LangFilesOrdering.UnexpectedComment + +$string['pluginname'] = 'OpenID Connect'; +$string['auth_oidcdescription'] = 'OpenID Connectプラグインは、設定可能なアイデンティティプロバイダを使用してシングルサインオン機能を提供します。'; +$string['cfg_authendpoint_key'] = '認証エンドポイント'; +$string['cfg_authendpoint_desc'] = 'アイデンティティプロバイダが使用する認証エンドポイントのURIです。'; +$string['cfg_autoappend_key'] = '自動付加'; +$string['cfg_autoappend_desc'] = 'ユーザがユーザ名/パスワードのログインフローを使用してログインした場合、自動的にこの文字列を付加します。これは、アイデンティティプロバイダが共通のドメインを求めているのものの、ユーザにログイン時に入力してほしくない場合に便利です。たとえば、完全なOpenID Connectユーザが"james@example.com"である場合、ここに"@example.com"と入力しておくと、ユーザはユーザ名として"james"を入力するだけで済みます。
注 : ユーザ名が競合する場合、つまり同じ名前のMoodleユーザが存在する場合、認証プラグインの優先順位を使用してユーザが決定されます。'; +$string['cfg_clientid_key'] = 'クライアントID'; +$string['cfg_clientid_desc'] = 'アイデンティティプロバイダに登録したクライアントID。'; +$string['cfg_clientsecret_key'] = 'クライアント秘密鍵'; +$string['cfg_clientsecret_desc'] = 'アイデンティティプロバイダに登録したクライアント秘密鍵です。プロバイダによっては、キーと呼ばれることもあります。'; +$string['cfg_err_invalidauthendpoint'] = '無効な認証エンドポイント'; +$string['cfg_err_invalidtokenendpoint'] = '無効なトークンエンドポイント'; +$string['cfg_err_invalidclientid'] = '無効なクライアントID'; +$string['cfg_err_invalidclientsecret'] = '無効なクライアント秘密鍵'; +$string['cfg_icon_key'] = 'アイコン'; +$string['cfg_icon_desc'] = 'ログインページでプロバイダ名の横に表示されるアイコンです。'; +$string['cfg_iconalt_o365'] = 'Microsoft 365アイコン'; +$string['cfg_iconalt_locked'] = 'ロック済みアイコン'; +$string['cfg_iconalt_lock'] = 'ロックアイコン'; +$string['cfg_iconalt_go'] = '緑の丸'; +$string['cfg_iconalt_stop'] = '赤の丸'; +$string['cfg_iconalt_user'] = 'ユーザアイコン'; +$string['cfg_iconalt_user2'] = '別のユーザアイコン'; +$string['cfg_iconalt_key'] = 'キーアイコン'; +$string['cfg_iconalt_group'] = 'グループアイコン'; +$string['cfg_iconalt_group2'] = '別のグループアイコン'; +$string['cfg_iconalt_mnet'] = 'MNETアイコン'; +$string['cfg_iconalt_userlock'] = 'ロック付きユーザアイコン'; +$string['cfg_iconalt_plus'] = 'プラスアイコン'; +$string['cfg_iconalt_check'] = 'チェックマークアイコン'; +$string['cfg_iconalt_rightarrow'] = '右向き矢印アイコン'; +$string['cfg_customicon_key'] = 'カスタムアイコン'; +$string['cfg_customicon_desc'] = '独自のアイコンを使用する場合は、ここにアップロードします。これにより、上記で選択していたアイコンは上書きされます。

カスタムアイコンを使用する際の注意 :
  • この画像はログインページではサイズ変更されません。このため、35x35ピクセル以下の画像をアップロードすることをお勧めします。
  • カスタムアイコンをアップロードした後で標準のアイコンに戻す場合は、上のボックスのカスタムアイコンをクリックします。次に[削除]、[OK]をクリックし、最後にフォームの下部にある[変更の保存]をクリックします。これにより、選択された標準アイコンがMoodleログインページに表示されます。
'; +$string['cfg_debugmode_key'] = 'デバッグメッセージを記録する'; +$string['cfg_debugmode_desc'] = '有効にすると、Moodleログに情報が記録されます。これは問題を特定するのに役立つことがあります。'; +$string['cfg_loginflow_key'] = 'ログインフロー'; +$string['cfg_loginflow_authcode'] = '認証リクエスト'; +$string['cfg_loginflow_authcode_desc'] = 'このフローでは、ユーザはMoodleログインページでアイデンティティプロバイダの名前 (上記の「プロバイダ名」を参照) をクリックします。ユーザはプロバイダにリダイレクトされ、そこでログインします。ログインが成功したら、ユーザはMoodleにリダイレクトされ、透過的にMoodleログインが行われます。これは最も標準化され、最もセキュアなユーザのログイン方法です。'; +$string['cfg_loginflow_rocreds'] = 'ユーザ名/パスワード認証'; +$string['cfg_loginflow_rocreds_desc'] = 'このフローでは、手動によるログインと同様、ユーザはMoodleのログインフォームにユーザ名とパスワードを入力します。これらの認証情報はバックグラウンドでアイデンティティプロバイダに渡され、認証を取得します。ユーザはアイデンティティプロバイダと直接やり取りしないので、このフローはユーザに最も透過的です。すべてのアイデンティティプロバイダがこのフローをサポートしているわけではない点にご注意ください。'; +$string['cfg_oidcresource_key'] = 'リソース'; +$string['cfg_oidcresource_desc'] = 'リクエストを送る、OpenID Connectのリソース。'; +$string['cfg_oidcscope_key'] = '範囲'; +$string['cfg_oidcscope_desc'] = '使用するOIDCスコープ。'; +$string['cfg_opname_key'] = 'プロバイダ名'; +$string['cfg_opname_desc'] = 'これはユーザがログインするために使用する必要がある認証情報の種類を識別するラベルで、エンドユーザに表示されます。このラベルはプロバイダを識別するために、このプラグインのユーザに表示されるすべての部分で使用されます。'; +$string['cfg_redirecturi_key'] = 'リダイレクトURI'; +$string['cfg_redirecturi_desc'] = 'これは"リダイレクトURI"として登録するURIです。OpenID Connectアイデンティティプロバイダは、クライアントとしてMoodleを登録するときにこれを要求します。
注意: これは、ここに表示されているとおり「正確」にOpenID Connectプロバイダに入力する必要があります。違いがあると、OpenID Connectを使用してログインできません。'; +$string['cfg_tokenendpoint_key'] = 'トークエンドポイント'; +$string['cfg_tokenendpoint_desc'] = 'アイデンティティプロバイダが使用する、トークンエンドポイントのURIです。'; +$string['event_debug'] = 'デバッグメッセージ'; +$string['errorauthdisconnectemptypassword'] = 'パスワードは空白にできません。'; +$string['errorauthdisconnectemptyusername'] = 'ユーザ名は空白にできません。'; +$string['errorauthdisconnectusernameexists'] = 'このユーザ名は既に使用されています。別のユーザ名を選択してください。'; +$string['errorauthdisconnectnewmethod'] = 'ログイン方法を使用する'; +$string['errorauthdisconnectinvalidmethod'] = '無効なログイン方法を受信しました。'; +$string['errorauthdisconnectifmanual'] = '手動によるログインを利用する場合は、以下に認証情報を入力します。'; +$string['errorauthinvalididtoken'] = 'Invalid id_tokenを受信しました。'; +$string['errorauthloginfailednouser'] = '無効なログイン : Moodleでユーザが見つかりませんでした'; +$string['errorauthnoauthcode'] = '認証コードを受信していません。'; +$string['errorauthnocreds'] = 'OpenID Connectクライアント認証情報を設定してください。'; +$string['errorauthnoendpoints'] = 'OpenID Connectサーバエンドポイントを設定してください。'; +$string['errorauthnohttpclient'] = 'HTTPクライアントを設定してください。'; +$string['errorauthnoidtoken'] = 'OpenID接続のid_tokenを受信していません。'; +$string['errorauthunknownstate'] = '不明な状態です。'; +$string['errorauthuseralreadyconnected'] = '既に別のOpenID Connectユーザに接続しています。'; +$string['errorauthuserconnectedtodifferent'] = '認証したOpenID Connectユーザは既にMoodleユーザに接続されています。'; +$string['errorbadloginflow'] = '無効なログインフローが指定されました。注 : インストールまたはアップグレードを最近行った場合は、Moodleキャッシュをクリアしてください。'; +$string['errorjwtbadpayload'] = 'JWTペイロードを読み取れませんでした。'; +$string['errorjwtcouldnotreadheader'] = 'JWTヘッダーを読み取れませんでした'; +$string['errorjwtempty'] = '空のJWT、または文字列以外のJWTを受信しました。'; +$string['errorjwtinvalidheader'] = '無効なJWTヘッダー'; +$string['errorjwtmalformed'] = '無効な形式のJWTを受信しました。'; +$string['errorjwtunsupportedalg'] = 'JWS AlgまたはJWEがサポートされていません'; +$string['erroroidcnotenabled'] = 'OpenID Connect認証プラグインが有効になっていません。'; +$string['errornodisconnectionauthmethod'] = 'フォールバックする有効な認証プラグインがないため、接続解除できません (ユーザの以前のログイン方法または手動ログイン方法)。'; +$string['erroroidcclientinvalidendpoint'] = '無効なエンドポイントURIを受信しました。'; +$string['erroroidcclientnocreds'] = 'クライアントの認証情報と秘密鍵を設定してください'; +$string['erroroidcclientnoauthendpoint'] = '認証エンドポイントが設定されていません。$this->setendpointsを使用して設定してください。'; +$string['erroroidcclientnotokenendpoint'] = 'トークンエンドポイントが設定されていません。$this->setendpointsを使用して設定してください。'; +$string['erroroidcclientinsecuretokenendpoint'] = 'トークンエンドポイントはこのためにSSL/TLSを使用している必要があります。'; +$string['errorucpinvalidaction'] = '無効なアクションを受信しました。'; +$string['erroroidccall'] = 'OpenID接続のエラーが発生しました。詳細については、ログを確認してください。'; +$string['erroroidccall_message'] = 'OpenID接続のエラー : {$a}'; +$string['eventuserauthed'] = 'ユーザをOpenID Coonectで認証しました'; +$string['eventusercreated'] = 'ユーザをOpenID Connectで作成しました'; +$string['eventuserconnected'] = 'ユーザをOpenID Connectに接続しました'; +$string['eventuserloggedin'] = 'ユーザはOpenID Connectにログインしました'; +$string['eventuserdisconnected'] = 'ユーザはOpenID Connectから接続解除されました'; +$string['oidc:manageconnection'] = 'OpenID Connect接続を管理する'; +$string['ucp_general_intro'] = 'ここでは、{$a} への接続を管理できます。有効にした場合、個別のユーザ名とパスワードを使用する代わりに、{$a} アカウントを使用してMoodleにログインできます。接続後は、Moodleのユーザ名とパスワードを覚えておく必要がなくなります。すべてのログインは {$a} が処理します。'; +$string['ucp_login_start'] = '{$a} を使用してMoodleへのログインを開始する'; +$string['ucp_login_start_desc'] = 'アカウントが {$a} を使用してMoodleにログインするよう切り替わります。有効にした場合、{$a} の認証情報を使用してログインするようになります。現在のMoodleユーザ名とパスワードは機能しなくなります。いつでもアカウントの接続を解除し、通常のログイン方法に戻ることができます。'; +$string['ucp_login_stop'] = '{$a} を使用したMoodleへのログインを停止する'; +$string['ucp_login_stop_desc'] = '現在 {$a} を使用してMoodleにログインしています。[{$a} ログインの停止]をクリックすると、 Moodleアカウントが {$a} から接続解除されます。{$a} アカウントを使用してMoodleにログインできなくなります。 ユーザ名とパスワードを作成するように求められます。その後は、Moodleに直接ログインできるようになります。'; +$string['ucp_login_status'] = '{$a} ログインは :'; +$string['ucp_status_enabled'] = '有効'; +$string['ucp_status_disabled'] = '無効'; +$string['ucp_disconnect_title'] = '{$a} 接続解除'; +$string['ucp_disconnect_details'] = 'Moodleアカウントを {$a} から接続解除します。Moodleにログインするには、ユーザ名とパスワードを作成する必要があります。'; +$string['ucp_title'] = '{$a} 管理'; + +// phpcs:enable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:enable moodle.Files.LangFilesOrdering.UnexpectedComment \ No newline at end of file diff --git a/auth/oidc/lang/nl/auth_oidc.php b/auth/oidc/lang/nl/auth_oidc.php new file mode 100644 index 00000000000..c79ec358556 --- /dev/null +++ b/auth/oidc/lang/nl/auth_oidc.php @@ -0,0 +1,133 @@ +. + +/** + * Dutch language strings. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +// phpcs:disable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:disable moodle.Files.LangFilesOrdering.UnexpectedComment + +$string['pluginname'] = 'OpenID Connect'; +$string['auth_oidcdescription'] = 'De OpenID Connect-plugin verschaft de mogelijkheid voor eenmalige aanmelding met configureerbare identiteitsproviders.'; +$string['cfg_authendpoint_key'] = 'Autorisatie-eindpunt'; +$string['cfg_authendpoint_desc'] = 'De URI van het token-eindpunt van je identiteitsprovider die je moet gebruiken.'; +$string['cfg_autoappend_key'] = 'Automatisch toevoegen'; +$string['cfg_autoappend_desc'] = 'Deze tekenreeks wordt automatisch toegevoegd wanneer gebruikers zich aanmelden met de aanmeldingsflow gebruikersnaam/wachtwoord. Dit is handig als je identiteitsprovider een algemeen domein vereist en je niet wilt dat gebruikers dit bij hun aanmelding moeten invoeren. Als de volledige OpenID Connect-gebruiker bijvoorbeeld \'jan@voorbeeld.com\' is en je hier @voorbeeld.com\' invoert, hoeft de gebruiker alleen \'jan\' als gebruikersnaam in te voeren.
Opmerking: als dezelfde gebruikersnamen bestaan, dus als er een Moodle-gebruiker met dezelfde naam bestaat, wordt de prioriteit van de authenticatie-plugin gebruikt om te bepalen welke gebruiker het wordt.'; +$string['cfg_clientid_key'] = 'Client-ID'; +$string['cfg_clientid_desc'] = 'Je client-ID die bij de identiteitsprovider is geregistreerd.'; +$string['cfg_clientsecret_key'] = 'Clientgeheim'; +$string['cfg_clientsecret_desc'] = 'Je clientgeheim dat bij de identiteitsprovider is geregistreerd. Bij sommige providers wordt dit een sleutel genoemd.'; +$string['cfg_err_invalidauthendpoint'] = 'Ongeldig autorisatie-eindpunt'; +$string['cfg_err_invalidtokenendpoint'] = 'Ongeldig token-eindpunt'; +$string['cfg_err_invalidclientid'] = 'Ongeldige client-ID'; +$string['cfg_err_invalidclientsecret'] = 'Ongeldig clientgeheim'; +$string['cfg_icon_key'] = 'Pictogram'; +$string['cfg_icon_desc'] = 'Een pictogram dat naast de naam van de provider op de aanmeldingspagina wordt weergegeven.'; +$string['cfg_iconalt_o365'] = 'Microsoft 365-pictogram'; +$string['cfg_iconalt_locked'] = 'Vergrendeld-pictogram'; +$string['cfg_iconalt_lock'] = 'Vergrendelingspictogram'; +$string['cfg_iconalt_go'] = 'Groene cirkel'; +$string['cfg_iconalt_stop'] = 'Rode cirkel'; +$string['cfg_iconalt_user'] = 'Gebruikerspictogram'; +$string['cfg_iconalt_user2'] = 'Gebruikerspictogram, alternatief'; +$string['cfg_iconalt_key'] = 'Sleutelpictogram'; +$string['cfg_iconalt_group'] = 'Groepspictogram'; +$string['cfg_iconalt_group2'] = 'Groepspictogram, alternatief'; +$string['cfg_iconalt_mnet'] = 'MNET-pictogram'; +$string['cfg_iconalt_userlock'] = 'Gebruiker met vergrendelingspictogram'; +$string['cfg_iconalt_plus'] = 'Plusteken'; +$string['cfg_iconalt_check'] = 'Vinkje'; +$string['cfg_iconalt_rightarrow'] = 'Pictogram pijl-rechts'; +$string['cfg_customicon_key'] = 'Aangepast pictogram'; +$string['cfg_customicon_desc'] = 'Als je een eigen pictogram wilt gebruiken, kun je dat hier uploaden. Je overschrijft daarmee een hierboven gekozen pictogram.

Opmerkingen bij het gebruik van aangepaste pictogrammen:
  • Het formaat van deze afbeelding wordt niet op de aanmeldingspagina aangepast. We raden je dan ook aan een afbeelding van maximaal 35 x 35 pixels te uploaden.
  • Als je een aangepast pictogram hebt geüpload en toch liever een van de standaardpictogrammen wilt gebruiken, klik dan in het vak hierboven op het aangepaste pictogram en klik op Verwijderen en op OK. Klik daarna onder in dit formulier op Wijzigingen opslaan. Het geselecteerde standaardpictogram wordt dan op de aanmeldingspagina van Moodle weergegeven.
'; +$string['cfg_debugmode_key'] = 'Foutopsporingsberichten registreren'; +$string['cfg_debugmode_desc'] = 'Als deze optie is ingeschakeld, worden gegevens in het Moodle-logboek geregistreerd die kunnen helpen bij het identificeren van problemen.'; +$string['cfg_loginflow_key'] = 'Aanmeldingsflow'; +$string['cfg_loginflow_authcode'] = 'Autorisatieverzoek'; +$string['cfg_loginflow_authcode_desc'] = 'In deze flow klikt de gebruiker op de Moodle-aanmeldingspagina op de naam van de identiteitsprovider (zie Naam provider hierboven), waarna de gebruiker naar de provider wordt omgeleid om zich aan te melden. Wanneer de gebruiker is aangemeld, wordt de gebruiker weer teruggeleid naar Moodle, waar de Moodle-aanmelding transparant wordt uitgevoerd. Dit is de meest gestandaardiseerde en veilige manier waarop de gebruiker zich kan aanmelden.'; +$string['cfg_loginflow_rocreds'] = 'Authenticatie met gebruikersnaam/wachtwoord'; +$string['cfg_loginflow_rocreds_desc'] = 'In deze flow voert de gebruiker zijn gebruikersnaam en wachtwoord in het aanmeldingsformulier van Moodle in, net als bij een handmatige aanmelding. De referenties van de gebruiker worden daarna op de achtergrond doorgegeven aan de identiteitsprovider om authenticatie te verkrijgen. Deze werkwijze is de meest transparante voor de gebruiker omdat er geen directe interactie is met de identiteitsprovider. Niet alle identiteitsproviders ondersteunen deze werkwijze.'; +$string['cfg_oidcresource_key'] = 'Bron'; +$string['cfg_oidcresource_desc'] = 'De OpenID Connect-bron waarvoor het verzoek moet worden verzonden.'; +$string['cfg_oidcscope_key'] = 'Reikwijdte'; +$string['cfg_oidcscope_desc'] = 'De te gebruiken OIDC-reikwijdte.'; +$string['cfg_opname_key'] = 'Naam provider'; +$string['cfg_opname_desc'] = 'Dit is een voor de gebruiker zichtbaar label dat aangeeft met welke type referenties de gebruiker zich moet aanmelden. Dit label wordt in alle voor de gebruiker zichtbare delen van deze plugin gebruikt om de provider aan te geven.'; +$string['cfg_redirecturi_key'] = 'Omleidings-URL'; +$string['cfg_redirecturi_desc'] = 'Dit is de URI voor registratie als de URI-omleiding. De identiteitsprovider van OpenID Connect vraagt hiernaar wanneer je Moodle als client registreert.
LET OP:je moet dit exact zo invullen in je OpenID Connect-provider als het hier wordt weergegeven. Als er verschil is, wordt aanmelding met Open ID Connect onmogelijk.'; +$string['cfg_tokenendpoint_key'] = 'Token-eindpunt'; +$string['cfg_tokenendpoint_desc'] = 'De URI van het token-eindpunt van je identiteitsprovider dat je moet gebruiken.'; +$string['event_debug'] = 'Foutopsporingsmelding'; +$string['errorauthdisconnectemptypassword'] = 'Wachtwoord kan niet leeg zijn'; +$string['errorauthdisconnectemptyusername'] = 'Gebruikersnaam kan niet leeg zijn'; +$string['errorauthdisconnectusernameexists'] = 'Deze gebruikersnaam wordt al gebruikt. Kies een andere.'; +$string['errorauthdisconnectnewmethod'] = 'Aanmeldingsmethode gebruiken'; +$string['errorauthdisconnectinvalidmethod'] = 'Ongeldige aanmeldingsmethode ontvangen.'; +$string['errorauthdisconnectifmanual'] = 'Voer hieronder je referenties in als je de handmatige aanmeldingsmethode gebruikt.'; +$string['errorauthinvalididtoken'] = 'Ongeldige id_token ontvangen.'; +$string['errorauthloginfailednouser'] = 'Ongeldige aanmelding: gebruiker niet gevonden in Moodle.'; +$string['errorauthnoauthcode'] = 'Authenticatiecode niet ontvangen.'; +$string['errorauthnocreds'] = 'Configureer de referenties van de OpenID Connect-client.'; +$string['errorauthnoendpoints'] = 'Configureer de eindpunten van de OpenID Connect-server.'; +$string['errorauthnohttpclient'] = 'Stel een HTTP-client in.'; +$string['errorauthnoidtoken'] = 'OpenID Connect id_token niet ontvangen.'; +$string['errorauthunknownstate'] = 'Onbekende toestand.'; +$string['errorauthuseralreadyconnected'] = 'Je bent al verbonden met een andere OpenID Connect-gebruiker.'; +$string['errorauthuserconnectedtodifferent'] = 'De geauthenticeerde OpenID Connect-gebruiker is al verbonden met een Moodle-gebruiker.'; +$string['errorbadloginflow'] = 'Ongeldige aanmeldingsflow opgegeven. Opmerking: maak je Moodle-cache leeg als je dit bericht ontvangt na een recente installatie of upgrade.'; +$string['errorjwtbadpayload'] = 'Kan JWT-payload niet lezen.'; +$string['errorjwtcouldnotreadheader'] = 'Kan JWT-header niet lezen'; +$string['errorjwtempty'] = 'JWT leeg of geen tekenreeks.'; +$string['errorjwtinvalidheader'] = 'Ongeldige JWT-header'; +$string['errorjwtmalformed'] = 'JWT met verkeerde indeling ontvangen.'; +$string['errorjwtunsupportedalg'] = 'JWS-algoritme of JWE niet ondersteund'; +$string['erroroidcnotenabled'] = 'De authenticatie-plugin van OpenID Connect is niet ingeschakeld.'; +$string['errornodisconnectionauthmethod'] = 'Kan verbinding niet verbreken omdat er geen ingeschakelde authenticatie-plugin is om op terug te vallen (vorige aanmeldingsmethode van gebruiker of handmatige aanmeldingsmethode).'; +$string['erroroidcclientinvalidendpoint'] = 'Ongeldige eindpunt-URI ontvangen.'; +$string['erroroidcclientnocreds'] = 'Stel clientreferenties in met setcreds'; +$string['erroroidcclientnoauthendpoint'] = 'Geen autorisatie-eindpunt ingesteld. Stel in met $this->setendpoints'; +$string['erroroidcclientnotokenendpoint'] = 'Geen token-eindpunt ingesteld. Stel in met $this->setendpoints'; +$string['erroroidcclientinsecuretokenendpoint'] = 'Het token-eindpunt moet hiervoor gebruikmaken van SSL/TLS.'; +$string['errorucpinvalidaction'] = 'Ongeldige actie ontvangen.'; +$string['erroroidccall'] = 'Fout in OpenID Connect. Controleer de logboeken voor meer informatie.'; +$string['erroroidccall_message'] = 'Fout in OpenID Connect: {$a}'; +$string['eventuserauthed'] = 'Gebruiker geautoriseerd met OpenID Connect'; +$string['eventusercreated'] = 'Gebruiker gemaakt met OpenID Connect'; +$string['eventuserconnected'] = 'Gebruiker verbonden met OpenID Connect'; +$string['eventuserloggedin'] = 'Gebruiker aangemeld met OpenID Connect'; +$string['eventuserdisconnected'] = 'Verbinding tussen gebruiker en OpenID Connect verbroken'; +$string['oidc:manageconnection'] = 'Verbinding met OpenID Connect beheren'; +$string['ucp_general_intro'] = 'Hier kun je de verbinding met {$a} beheren. Als deze optie is ingeschakeld, kun je je bij Moodle aanmelden met je {$a}-account in plaats van een aparte gebruikersnaam en wachtwoord. Als de verbinding is gemaakt, hoef je je gebruikersnaam en wachtwoord voor Moodle niet meer te onthouden. Alle aanmeldingen worden afgehandeld door {$a}.'; +$string['ucp_login_start'] = '{$a} gebruiken om je aan te melden bij Moodle'; +$string['ucp_login_start_desc'] = 'Hiermee stel je je account in voor het het gebruik van {$a} om je aan te melden bij Moodle. Als deze optie is ingeschakeld, meld je je aan met je {$a}-referenties. Je huidige Moodle-gebruikersnaam en -wachtwoord werken niet meer. Je kunt op elk moment de verbinding met je account verbreken en terugkeren naar de normale aanmelding.'; +$string['ucp_login_stop'] = '{$a} niet meer gebruiken om je aan te melden bij Moodle'; +$string['ucp_login_stop_desc'] = 'Op dit moment gebruik je {$a} om je aan te melden bij Moodle. Als je op {$a} niet meer gebruiken om je aan te melden klikt, wordt de verbinding tussen je Moodle-account en {$a} verbroken. Je kunt je niet meer bij Moodle aanmelden met je {$a}-account. Je wordt gevraagd een gebruikersnaam en wachtwoord op te geven, en vanaf dat moment kun je je direct aanmelden bij Moodle.'; +$string['ucp_login_status'] = '{$a}-aanmelding is:'; +$string['ucp_status_enabled'] = 'Ingeschakeld'; +$string['ucp_status_disabled'] = 'Uitgeschakeld'; +$string['ucp_disconnect_title'] = 'Verbinding met {$a} verbroken'; +$string['ucp_disconnect_details'] = 'Hiermee wordt de verbinding tussen je Moodle-account en {$a} verbroken. Je moet een gebruikersnaam en wachtwoord maken om je aan te melden bij Moodle.'; +$string['ucp_title'] = '{$a}-beheer'; + +// phpcs:enable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:enable moodle.Files.LangFilesOrdering.UnexpectedComment \ No newline at end of file diff --git a/auth/oidc/lang/pl/auth_oidc.php b/auth/oidc/lang/pl/auth_oidc.php new file mode 100644 index 00000000000..0280a400982 --- /dev/null +++ b/auth/oidc/lang/pl/auth_oidc.php @@ -0,0 +1,133 @@ +. + +/** + * Polish language strings. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +// phpcs:disable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:disable moodle.Files.LangFilesOrdering.UnexpectedComment + +$string['pluginname'] = 'OpenID Connect'; +$string['auth_oidcdescription'] = 'Wtyczka OpenID Connect udostępnia funkcję rejestracji jednokrotnej przy użyciu dostawców tożsamości, których można skonfigurować.'; +$string['cfg_authendpoint_key'] = 'Punkt końcowy autoryzacji'; +$string['cfg_authendpoint_desc'] = 'Identyfikator URI punktu końcowego autoryzacji od dostawcy tożsamości do wykorzystania.'; +$string['cfg_autoappend_key'] = 'Dołączaj automatycznie'; +$string['cfg_autoappend_desc'] = 'Automatycznie dołączaj ten ciąg przy logowaniu użytkowników za pomocą nazwy użytkownika/hasła. Jest to przydatne, gdy dostawca tożsamości wymaga stosowania wspólnej domeny, ale nie chce wymagać jej wpisywania przez użytkowników podczas logowania. Na przykład jeżeli pełna nazwa użytkownika wtyczki OpenID Connect to „jan@przyklad.com”, a w tym polu zostanie wprowadzony ciąg „@przyklad.com”, użytkownik będzie musiał jedynie wpisać słowo „jan” jako swoją nazwę użytkownika.
Uwaga: Jeżeli istnieją nazwy użytkowników powodujące konflikt, np. jeżeli istnieje użytkownik platformy Moodle o tej samej nazwie, do określenia, który użytkownik ma pierwszeństwo stosowane są ustawienia priorytetów wtyczki uwierzytelniania.'; +$string['cfg_clientid_key'] = 'Identyfikator klienta'; +$string['cfg_clientid_desc'] = 'Zarejestrowany identyfikator klienta w dostawcy tożsamości.'; +$string['cfg_clientsecret_key'] = 'Tajny klucz klienta'; +$string['cfg_clientsecret_desc'] = 'Zarejestrowany tajny klucz klienta w dostawcy tożsamości. W przypadku niektórych dostawców tożsamości jest on również określany jako klucz.'; +$string['cfg_err_invalidauthendpoint'] = 'Nieprawidłowy punkt końcowy autoryzacji'; +$string['cfg_err_invalidtokenendpoint'] = 'Nieprawidłowy punkt końcowy tokenu'; +$string['cfg_err_invalidclientid'] = 'Nieprawidłowy identyfikator klienta'; +$string['cfg_err_invalidclientsecret'] = 'Nieprawidłowy tajny klucz klienta'; +$string['cfg_icon_key'] = 'Ikona'; +$string['cfg_icon_desc'] = 'Ikona do wyświetlania obok nazwy dostawcy na stronie logowania.'; +$string['cfg_iconalt_o365'] = 'Ikona pakietu Microsoft 365'; +$string['cfg_iconalt_locked'] = 'Ikona zablokowana'; +$string['cfg_iconalt_lock'] = 'Ikona blokady'; +$string['cfg_iconalt_go'] = 'Zielony okrąg'; +$string['cfg_iconalt_stop'] = 'Czerwony okrąg'; +$string['cfg_iconalt_user'] = 'Ikona użytkownika'; +$string['cfg_iconalt_user2'] = 'Alternatywna ikona użytkownika'; +$string['cfg_iconalt_key'] = 'Ikona klucza'; +$string['cfg_iconalt_group'] = 'Ikona grupy'; +$string['cfg_iconalt_group2'] = 'Alternatywna ikona grupy'; +$string['cfg_iconalt_mnet'] = 'Ikona MNET'; +$string['cfg_iconalt_userlock'] = 'Użytkownik z ikoną blokady'; +$string['cfg_iconalt_plus'] = 'Ikona znaku plus'; +$string['cfg_iconalt_check'] = 'Ikona znaku wyboru'; +$string['cfg_iconalt_rightarrow'] = 'Ikona strzałki w prawo'; +$string['cfg_customicon_key'] = 'Niestandardowa ikona'; +$string['cfg_customicon_desc'] = 'Jeżeli użytkownik chce użyć własnej ikony, może ją przesłać za pomocą tej opcji. Nową ikoną można zastąpić dowolną ikonę wybraną powyżej.

Uwagi dotyczące używania niestandardowych ikon:
  • Rozmiar tego obrazu nie zostanie zmieniony na stronie logowania, zatem zalecamy załadowanie obrazu o maksymalnym rozmiarze 35x35 pikseli.
  • Jeżeli użytkownik przesłał niestandardową ikonę i chce przywrócić jedną ze standardowych ikon programu, należy kliknąć ikonę niestandardową w polu powyżej i kliknąć przycisk „Usuń”. Następnie należy kliknąć przycisk „OK” oraz przycisk „Zapisz zmiany” w dolnej części formularza. Wybrana ikona standardowa będzie wyświetlana na stronie logowania do platformy Moodle.
'; +$string['cfg_debugmode_key'] = 'Rejestruj komunikaty debugowania'; +$string['cfg_debugmode_desc'] = 'Jeśli ta opcja jest włączona, informacje będą rejestrowane w pliku dziennika platformy Moodle, aby pomóc w identyfikacji problemów.'; +$string['cfg_loginflow_key'] = 'Przepływ logowania'; +$string['cfg_loginflow_authcode'] = 'Żądanie autoryzacji'; +$string['cfg_loginflow_authcode_desc'] = 'W przypadku tego przepływu użytkownik klika nazwę dostawcy tożsamości (patrz „Nazwa dostawcy” powyżej) na stronie logowania do platformy Moodle i zostaje przekierowany do dostawcy, aby się zalogować. Po pomyślnym zalogowaniu użytkownik jest ponownie przekierowywany do strony platformy Moodle, na której odbywa się logowanie do platformy Moodle w sposób niewidoczny. Jest to najbardziej ustandaryzowany i bezpieczny sposób logowania się użytkownika.'; +$string['cfg_loginflow_rocreds'] = 'Uwierzytelnienie nazwy użytkownika/hasła'; +$string['cfg_loginflow_rocreds_desc'] = 'W przypadku tego przepływu użytkownik wprowadza nazwę użytkownika i hasło do formularza logowania się do platformy Moodle w taki sam sposób jak w przypadku logowania ręcznego. Dane logowania użytkownika są następnie przesyłane do dostawcy tożsamości w tle w celu uwierzytelnienia. Ten przepływ jest najbardziej niewidoczny dla użytkownika, ponieważ użytkownik nie wchodzi w bezpośrednią interakcję z dostawcą tożsamości. Nie wszyscy dostawcy tożsamości obsługują ten przepływ.'; +$string['cfg_oidcresource_key'] = 'Zasób'; +$string['cfg_oidcresource_desc'] = 'Zasób wtyczki OpenID Connect, do którego ma zostać wysłane żądanie.'; +$string['cfg_oidcscope_key'] = 'Scope'; +$string['cfg_oidcscope_desc'] = 'Zakres OIDC do użycia.'; +$string['cfg_opname_key'] = 'Nazwa dostawcy'; +$string['cfg_opname_desc'] = 'Jest to etykieta dla użytkownika końcowego określająca rodzaj danych logowania, których użytkownik musi użyć do logowania. Ta etykieta jest używana w obszarach wtyczki widocznych dla użytkownika w celu zidentyfikowania dostawcy.'; +$string['cfg_redirecturi_key'] = 'Adres URI przekierowania'; +$string['cfg_redirecturi_desc'] = 'Jest to identyfikator URI, który należy zarejestrować jako „Adres URI przekierowania”. Dostawca tożsamości wtyczki OpenID Connect powinien zapytać o ten identyfikator podczas rejestracji platformy Moodle jako klienta.
UWAGA: Należy go wprowadzić do dostawcy OpenID Connect *dokładnie* w takiej postaci, w jakiej występuje w tym miejscu. Dowolna różnica uniemożliwi logowanie za pomocą OpenID Connect.'; +$string['cfg_tokenendpoint_key'] = 'Punkt końcowy tokenu'; +$string['cfg_tokenendpoint_desc'] = 'Adres URI punktu końcowego tokenu od dostawcy tożsamości do wykorzystania.'; +$string['event_debug'] = 'Komunikaty debugowania'; +$string['errorauthdisconnectemptypassword'] = 'Hasło nie może być puste'; +$string['errorauthdisconnectemptyusername'] = 'Pole nazwy użytkownika nie może być puste'; +$string['errorauthdisconnectusernameexists'] = 'Ta nazwa użytkownika jest już zajęta. Wybierz inną nazwę.'; +$string['errorauthdisconnectnewmethod'] = 'Użyj sposobu logowania'; +$string['errorauthdisconnectinvalidmethod'] = 'Otrzymano nieprawidłowy sposób logowania.'; +$string['errorauthdisconnectifmanual'] = 'W przypadku korzystania z ręcznego sposobu logowania wprowadź dane logowania poniżej.'; +$string['errorauthinvalididtoken'] = 'Otrzymano nieprawidłowy token identyfikatora.'; +$string['errorauthloginfailednouser'] = 'Nieprawidłowe dane logowania: nie znaleziono użytkownika w platformie Moodle.'; +$string['errorauthnoauthcode'] = 'Nie otrzymano kodu uwierzytelniania.'; +$string['errorauthnocreds'] = 'Skonfiguruj dane logowania klienta wtyczki OpenID Connect.'; +$string['errorauthnoendpoints'] = 'Skonfiguruj punkty końcowe serwera wtyczki OpenID Connect.'; +$string['errorauthnohttpclient'] = 'Ustaw klienta HTTP.'; +$string['errorauthnoidtoken'] = 'Nie otrzymano tokenu identyfikatora OpenID Connect.'; +$string['errorauthunknownstate'] = 'Stan nieznany.'; +$string['errorauthuseralreadyconnected'] = 'Połączono już z innym użytkownikiem wtyczki OpenID Connect.'; +$string['errorauthuserconnectedtodifferent'] = 'Uwierzytelniony użytkownik wtyczki OpenID Connect jest już połączony z użytkownikiem platformy Moodle.'; +$string['errorbadloginflow'] = 'Określono nieprawidłowy przepływ logowania. Uwaga: jeżeli ten komunikat jest wyświetlany po niedawnej instalacji lub aktualizacji, należy wyczyścić pamięć podręczną platformy Moodle.'; +$string['errorjwtbadpayload'] = 'Nie udało się odczytać ładunku tokenu JWT.'; +$string['errorjwtcouldnotreadheader'] = 'Nie udało się odczytać nagłówka tokenu JWT'; +$string['errorjwtempty'] = 'Otrzymano token JWT, który jest pusty lub nie jest ciągiem.'; +$string['errorjwtinvalidheader'] = 'Nieprawidłowy nagłówek tokenu JWT'; +$string['errorjwtmalformed'] = 'Otrzymano nieprawidłowo utworzony token JWT.'; +$string['errorjwtunsupportedalg'] = 'Tokeny JWS Alg lub JWE nie są obsługiwane'; +$string['erroroidcnotenabled'] = 'Wtyczka uwierzytelniania OpenID Connect nie jest włączona.'; +$string['errornodisconnectionauthmethod'] = 'Nie można odłączyć, ponieważ żadna wtyczka uwierzytelniania nie jest włączona (poprzedni sposób logowania użytkownika lub sposób logowania ręcznego).'; +$string['erroroidcclientinvalidendpoint'] = 'Otrzymano nieprawidłowy adres URI punktu końcowego.'; +$string['erroroidcclientnocreds'] = 'Ustaw dane logowania klienta przy pomocy ustawionych danych logowania'; +$string['erroroidcclientnoauthendpoint'] = 'Nie ustawiono punktu końcowego autoryzacji. Ustaw przy użyciu $this->setendpoints'; +$string['erroroidcclientnotokenendpoint'] = 'Nie ustawiono punktu końcowego tokenu. Ustaw przy użyciu $this->setendpoints'; +$string['erroroidcclientinsecuretokenendpoint'] = 'Punkt końcowy musi w tym celu korzystać z certyfikatu SSL/TLS.'; +$string['errorucpinvalidaction'] = 'Otrzymano nieprawidłowe działanie.'; +$string['erroroidccall'] = 'Błąd w OpenID Connect. Więcej informacji można znaleźć w dziennikach.'; +$string['erroroidccall_message'] = 'Błąd w OpenID Connect: {$a}'; +$string['eventuserauthed'] = 'Zautoryzowano użytkownika przy użyciu wtyczki OpenID Connect'; +$string['eventusercreated'] = 'Utworzono użytkownika we wtyczce OpenID Connect'; +$string['eventuserconnected'] = 'Podłączono użytkownika do wtyczki OpenID Connect'; +$string['eventuserloggedin'] = 'Zalogowano użytkownika za pomocą wtyczki OpenID Connect'; +$string['eventuserdisconnected'] = 'Odłączono użytkownika od wtyczki OpenID Connect'; +$string['oidc:manageconnection'] = 'Zarządzaj połączeniem z wtyczką OpenID Connect'; +$string['ucp_general_intro'] = 'Ta opcja umożliwia zarządzanie połączeniem z {$a}. Jeżeli jest włączona, użytkownik może korzystać z konta {$a} do logowania się do platformy Moodle zamiast używania oddzielnej nazwy użytkownika i hasła. Po połączeniu nie trzeba będzie pamiętać nazwy użytkownika ani hasła do platformy Moodle — wszystkie operacje logowania będą obsługiwane przez {$a}.'; +$string['ucp_login_start'] = 'Używaj {$a} w celu logowania się do platformy Moodle'; +$string['ucp_login_start_desc'] = 'Spowoduje to przełączenie konta na logowanie się do platformy Moodle przy użyciu {$a}. Po włączeniu tej opcji użytkownik będzie się logował przy użyciu danych logowania {$a} — bieżąca nazwa użytkownika i hasło do platformy Moodle nie będą działać. Można odłączyć konto w dowolnym momencie i powrócić do zwykłego sposobu logowania się.'; +$string['ucp_login_stop'] = 'Nie używaj {$a} w celu logowania się do platformy Moodle'; +$string['ucp_login_stop_desc'] = 'Obecnie używasz {$a}, aby logować się do platformy Moodle. Kliknij opcję „Nie używaj logowania {$a}”, aby odłączyć konto na platformie Moodle od {$a}. Logowanie do platformy Moodle przy użyciu konta {$a} nie będzie możliwe. Musisz utworzyć nazwę użytkownika i hasło, aby zalogować się do platformy Moodle bezpośrednio.'; +$string['ucp_login_status'] = 'Login {$a} to:'; +$string['ucp_status_enabled'] = 'Włączone'; +$string['ucp_status_disabled'] = 'Wyłączone'; +$string['ucp_disconnect_title'] = 'Rozłączono {$a}'; +$string['ucp_disconnect_details'] = 'Spowoduje to odłączenie konta na platformie Moodle od {$a}. Konieczne będzie utworzenie nazwy użytkownika i hasła w celu zalogowania się do platformy Moodle.'; +$string['ucp_title'] = 'Zarządzanie {$a}'; + +// phpcs:enable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:enable moodle.Files.LangFilesOrdering.UnexpectedComment \ No newline at end of file diff --git a/auth/oidc/lang/pt_br/auth_oidc.php b/auth/oidc/lang/pt_br/auth_oidc.php new file mode 100644 index 00000000000..1d2218978d8 --- /dev/null +++ b/auth/oidc/lang/pt_br/auth_oidc.php @@ -0,0 +1,133 @@ +. + +/** + * Portuguese - Brazil language strings. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +// phpcs:disable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:disable moodle.Files.LangFilesOrdering.UnexpectedComment + +$string['pluginname'] = 'OpenID Connect'; +$string['auth_oidcdescription'] = 'O plugin OpenID Connect oferece o recurso de logon único usando provedores de identidade que podem ser configurados.'; +$string['cfg_authendpoint_key'] = 'Ponto de extremidade de autorização'; +$string['cfg_authendpoint_desc'] = 'O URI do ponto de extremidade de autorização do seu provedor de identidade a ser usado.'; +$string['cfg_autoappend_key'] = 'Acrescentar automaticamente'; +$string['cfg_autoappend_desc'] = 'Acrescente essa cadeia de caracteres automaticamente ao efetuar o login de usuários utilizando o fluxo de login com nome de usuário e senha. Isso é útil quando seu provedor de identidade exige um domínio comum, mas não quer exigir que os usuários o digitem ao fazer login. Por exemplo, se o usuário completo do OpenID Connect for "joao@exemplo.com" e você inserir "@exemplo.com" aqui, o usuário só precisará inserir "joao" como nome de usuário.
Observação: caso exista conflito entre nomes de usuários, ou seja, exista um usuário do Moodle com o mesmo nome, a prioridade do plugin de autenticação é usada para determinar qual usuário prevalecerá.'; +$string['cfg_clientid_key'] = 'ID do cliente'; +$string['cfg_clientid_desc'] = 'Seu ID do cliente registrado no provedor de identidade.'; +$string['cfg_clientsecret_key'] = 'Segredo do cliente'; +$string['cfg_clientsecret_desc'] = 'Seu segredo do cliente registrado no provedor de identidade. Em alguns provedores, ele também é chamado de chave.'; +$string['cfg_err_invalidauthendpoint'] = 'Ponto de extremidade de autorização inválido'; +$string['cfg_err_invalidtokenendpoint'] = 'Ponto de extremidade de token inválido'; +$string['cfg_err_invalidclientid'] = 'ID do cliente inválido'; +$string['cfg_err_invalidclientsecret'] = 'Segredo do cliente inválido'; +$string['cfg_icon_key'] = 'Ícone'; +$string['cfg_icon_desc'] = 'Um ícone a ser exibido ao lado do nome do provedor na página de login.'; +$string['cfg_iconalt_o365'] = 'Ícone do Microsoft 365'; +$string['cfg_iconalt_locked'] = 'Ícone de bloqueado'; +$string['cfg_iconalt_lock'] = 'Ícone de bloqueio'; +$string['cfg_iconalt_go'] = 'Círculo verde'; +$string['cfg_iconalt_stop'] = 'Círculo vermelho'; +$string['cfg_iconalt_user'] = 'Ícone do usuário'; +$string['cfg_iconalt_user2'] = 'Ícone alternativo do usuário'; +$string['cfg_iconalt_key'] = 'Ícone de chave'; +$string['cfg_iconalt_group'] = 'Ícone do grupo'; +$string['cfg_iconalt_group2'] = 'Ícone alternativo do grupo'; +$string['cfg_iconalt_mnet'] = 'Ícone da MNET'; +$string['cfg_iconalt_userlock'] = 'Ícone de usuário com bloqueio'; +$string['cfg_iconalt_plus'] = 'Ícone de sinal de adição'; +$string['cfg_iconalt_check'] = 'Ícone de marca de seleção'; +$string['cfg_iconalt_rightarrow'] = 'Ícone de seta para a direita'; +$string['cfg_customicon_key'] = 'Ícone personalizado'; +$string['cfg_customicon_desc'] = 'Se você quiser usar seu próprio ícone, faça o upload dele aqui. Isso substituirá qualquer ícone escolhido acima.

Observações sobre o uso de ícones personalizados:
  • Essa imagem não será redimensionada na página de login; portanto, recomendamos o upload de uma imagem de, no máximo, 35x35 pixels.
  • Caso você tenha feito o upload de um ícone personalizado e queira voltar a usar um dos ícones padrão, clique no ícone personalizado na caixa acima, em "Excluir", em "OK" e depois clique em "Salvar alterações" na parte inferior deste formulário. Agora o ícone padrão selecionado será exibido na página de login do Moodle.
'; +$string['cfg_debugmode_key'] = 'Registrar mensagens de depuração'; +$string['cfg_debugmode_desc'] = 'Se essa configuração estiver ativada, informações que podem ajudar a identificar problemas serão registradas no log do Moodle.'; +$string['cfg_loginflow_key'] = 'Fluxo de login'; +$string['cfg_loginflow_authcode'] = 'Solicitação de autorização'; +$string['cfg_loginflow_authcode_desc'] = 'Ao usar esse fluxo, o usuário clicará no nome do provedor de identidade (consulte "Nome do provedor" acima) na página de login do Moodle e será redirecionado para o provedor para fazer login. Depois de efetuar com sucesso o login, o usuário será redirecionado de volta para o Moodle, onde o login ocorrerá de modo transparente. Essa é a maneira mais padronizada e segura de realizar o login do usuário.'; +$string['cfg_loginflow_rocreds'] = 'Autenticação de nome de usuário e senha'; +$string['cfg_loginflow_rocreds_desc'] = 'Ao usar esse fluxo, o usuário informará seu nome de usuário e sua senha no formulário de login do Moodle da mesma forma que faria em um login manual. As credenciais serão, então, transmitidas em segundo plano para o provedor de identidade no intuito de obter a autenticação. Esse fluxo é o mais simples para o usuário, pois ele não interage diretamente com o provedor de identidade. Tenha em mente que nem todos os provedores de identidade aceitam a utilização desse fluxo.'; +$string['cfg_oidcresource_key'] = 'Recurso'; +$string['cfg_oidcresource_desc'] = 'O recurso do OpenID Connect para o qual a solicitação deverá ser enviada.'; +$string['cfg_oidcscope_key'] = 'Escopo'; +$string['cfg_oidcscope_desc'] = 'O escopo do OIDC a ser usado.'; +$string['cfg_opname_key'] = 'Nome do provedor'; +$string['cfg_opname_desc'] = 'Esse é um rótulo visível para o usuário que identifica o tipo de credenciais que devem ser utilizadas pelo usuário no login. Esse rótulo é usado em todas as partes visíveis para o usuário deste plugin para a identificação do seu provedor.'; +$string['cfg_redirecturi_key'] = 'URI de redirecionamento'; +$string['cfg_redirecturi_desc'] = 'Esse é o URI a ser registrado como o "URI de redirecionamento". Seu provedor de identidade do OpenID Connect deve solicitá-lo ao registrar o Moodle como cliente.
OBSERVAÇÃO: é necessário inserir essa informação no seu provedor do OpenID Connect EXATAMENTE como ela é exibida aqui. Qualquer diferença impedirá que logins sejam efetuados usando o OpenID Connect.'; +$string['cfg_tokenendpoint_key'] = 'Ponto de extremidade de token'; +$string['cfg_tokenendpoint_desc'] = 'O URI do ponto de extremidade de token do seu provedor de identidade a ser usado.'; +$string['event_debug'] = 'Mensagem de depuração'; +$string['errorauthdisconnectemptypassword'] = 'A senha não pode ficar em branco'; +$string['errorauthdisconnectemptyusername'] = 'O nome de usuário não pode ficar em branco'; +$string['errorauthdisconnectusernameexists'] = 'Esse nome de usuário já está em uso. Escolha outro nome.'; +$string['errorauthdisconnectnewmethod'] = 'Usar método de login'; +$string['errorauthdisconnectinvalidmethod'] = 'Método de login inválido recebido.'; +$string['errorauthdisconnectifmanual'] = 'Se estiver usando o método de login manual, insira as credenciais abaixo.'; +$string['errorauthinvalididtoken'] = 'id_token inválido recebido.'; +$string['errorauthloginfailednouser'] = 'Login inválido: usuário não encontrado no Moodle.'; +$string['errorauthnoauthcode'] = 'Código de autorização não recebido.'; +$string['errorauthnocreds'] = 'Configure as credenciais de cliente do OpenID Connect.'; +$string['errorauthnoendpoints'] = 'Configure os pontos de extremidade de servidor do OpenID Connect.'; +$string['errorauthnohttpclient'] = 'Defina um cliente de HTTP.'; +$string['errorauthnoidtoken'] = 'O id_token do OpenID Connect não foi recebido.'; +$string['errorauthunknownstate'] = 'Estado desconhecido.'; +$string['errorauthuseralreadyconnected'] = 'Você já está conectado a um usuário diferente do OpenID Connect.'; +$string['errorauthuserconnectedtodifferent'] = 'O usuário do OpenID Connect que realizou a autenticação já está conectado a um usuário do Moodle.'; +$string['errorbadloginflow'] = 'Fluxo de login inválido especificado. Observação: se você recebeu esta mensagem após uma instalação ou atualização recente, limpe seu cache do Moodle.'; +$string['errorjwtbadpayload'] = 'Não foi possível ler o conteúdo de JWT.'; +$string['errorjwtcouldnotreadheader'] = 'Não foi possível ler o cabeçalho de JWT.'; +$string['errorjwtempty'] = 'Cadeia de caracteres vazia ou inválida de JWT recebida.'; +$string['errorjwtinvalidheader'] = 'Cabeçalho de JWT inválido'; +$string['errorjwtmalformed'] = 'JWT malformado recebido.'; +$string['errorjwtunsupportedalg'] = 'JWS Alg ou JWE não compatível'; +$string['erroroidcnotenabled'] = 'O plugin de autenticação do OpenID Connect não está ativado.'; +$string['errornodisconnectionauthmethod'] = 'Não é possível se desconectar, pois não há plugin de autenticação ativado ao qual retornar (o método de login anterior do usuário ou o método de login manual).'; +$string['erroroidcclientinvalidendpoint'] = 'URI de ponto de extremidade inválido recebido.'; +$string['erroroidcclientnocreds'] = 'Defina as credenciais de cliente com setcreds'; +$string['erroroidcclientnoauthendpoint'] = 'Nenhum ponto de extremidade de autorização definido. Defina-o com $this->setendpoints'; +$string['erroroidcclientnotokenendpoint'] = 'Nenhum ponto de extremidade de token definido. Defina-o com $this->setendpoints'; +$string['erroroidcclientinsecuretokenendpoint'] = 'Para isso, é necessário que o ponto de extremidade de token esteja usando SSL/TLS.'; +$string['errorucpinvalidaction'] = 'Ação inválida recebida.'; +$string['erroroidccall'] = 'Erro no OpenID Connect. Verifique os logs para obter mais informações.'; +$string['erroroidccall_message'] = 'Erro no OpenID Connect: {$a}'; +$string['eventuserauthed'] = 'Usuário autorizado com o OpenID Connect'; +$string['eventusercreated'] = 'Usuário criado com o OpenID Connect'; +$string['eventuserconnected'] = 'Usuário conectado ao OpenID Connect'; +$string['eventuserloggedin'] = 'Usuário com login efetuado no OpenID Connect'; +$string['eventuserdisconnected'] = 'Usuário desconectado do OpenID Connect'; +$string['oidc:manageconnection'] = 'Gerenciar conexão ao OpenID Connect'; +$string['ucp_general_intro'] = 'Aqui você pode gerenciar sua conexão ao {$a}. Se essa configuração estiver ativada, você poderá usar sua conta do {$a} para fazer login no Moodle em vez de precisar de nome de usuário e senha separados. Depois que estiver conectado, você não precisará mais se lembrar de um nome de usuário e uma senha para o Moodle, pois todos os logins serão administrados pelo {$a}.'; +$string['ucp_login_start'] = 'Começar a usar o {$a} para fazer login no Moodle'; +$string['ucp_login_start_desc'] = 'Essa configuração fará uma alteração na sua conta, que passará a usar o {$a} para fazer login no Moodle. Depois de ativada, você fará login usando suas credenciais do {$a}; seu nome de usuário e sua senha do Moodle não serão aceitos. Você pode desconectar sua conta quando quiser e voltar a fazer login como antes.'; +$string['ucp_login_stop'] = 'Parar de usar o {$a} para fazer login no Moodle'; +$string['ucp_login_stop_desc'] = 'No momento, você está usando o {$a} para fazer login no Moodle. Ao clicar em "Para de usar o login do {$a}", você desconectará sua conta do Moodle do {$a}. Você não poderá mais fazer login no Moodle com sua conta do {$a} e precisará criar um nome de usuário e uma senha para poder fazer login diretamente no Moodle.'; +$string['ucp_login_status'] = 'O login via {$a} está:'; +$string['ucp_status_enabled'] = 'Ativado'; +$string['ucp_status_disabled'] = 'Desativado'; +$string['ucp_disconnect_title'] = 'Desconexão do {$a}'; +$string['ucp_disconnect_details'] = 'Essa ação desconectará sua conta do Moodle do {$a}. Você precisará criar um nome de usuário e uma senha para fazer login no Moodle.'; +$string['ucp_title'] = 'Gerenciamento do {$a}'; + +// phpcs:enable moodle.Files.LangFilesOrdering.IncorrectOrder +// phpcs:enable moodle.Files.LangFilesOrdering.UnexpectedComment \ No newline at end of file diff --git a/auth/oidc/lib.php b/auth/oidc/lib.php new file mode 100644 index 00000000000..482060daafb --- /dev/null +++ b/auth/oidc/lib.php @@ -0,0 +1,772 @@ +. + +/** + * Plugin library. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +use auth_oidc\jwt; +use auth_oidc\utils; + +// IdP types. +/** + * Microsoft Entra ID identity provider type. + */ +const AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID = 1; + +/** + * Microsoft Identity Platform identity provider type. + */ +const AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM = 2; + +/** + * Other identity provider type. + */ +const AUTH_OIDC_IDP_TYPE_OTHER = 3; + +// Microsoft Entra ID / Microsoft endpoint version. +/** + * Unknown Microsoft endpoint version. + */ +const AUTH_OIDC_MICROSOFT_ENDPOINT_VERSION_UNKNOWN = 0; + +/** + * Microsoft endpoint version 1. + */ +const AUTH_OIDC_MICROSOFT_ENDPOINT_VERSION_1 = 1; + +/** + * Microsoft endpoint version 2. + */ +const AUTH_OIDC_MICROSOFT_ENDPOINT_VERSION_2 = 2; + +// OIDC application authentication method. +/** + * OIDC application authentication method using secret. + */ +const AUTH_OIDC_AUTH_METHOD_SECRET = 1; + +/** + * OIDC application authentication method using certificate. + */ +const AUTH_OIDC_AUTH_METHOD_CERTIFICATE = 2; + +// OIDC application auth certificate source. +/** + * OIDC application authentication certificate source from text. + */ +const AUTH_OIDC_AUTH_CERT_SOURCE_TEXT = 1; + +/** + * OIDC application authentication certificate source from file. + */ +const AUTH_OIDC_AUTH_CERT_SOURCE_FILE = 2; + +/** + * Initialize custom icon for OIDC authentication. + * + * This function sets up a custom icon for the OIDC plugin by creating necessary directories + * and copying the file into the specified location in Moodle's data directory. + * + * @param string $filefullname Full name of the custom icon file. + * @return bool False if the file is missing or is a directory; void otherwise. + */ +function auth_oidc_initialize_customicon($filefullname) { + global $CFG; + + $file = get_config('auth_oidc', 'customicon'); + $systemcontext = \context_system::instance(); + $fullpath = "/{$systemcontext->id}/auth_oidc/customicon/0{$file}"; + + $fs = get_file_storage(); + if (!($file = $fs->get_file_by_hash(sha1($fullpath))) || $file->is_directory()) { + return false; + } + $pixpluginsdir = 'pix_plugins/auth/oidc/0'; + $pixpluginsdirparts = explode('/', $pixpluginsdir); + $curdir = $CFG->dataroot; + foreach ($pixpluginsdirparts as $dir) { + $curdir .= '/' . $dir; + if (!file_exists($curdir)) { + mkdir($curdir); + } + } + + if (file_exists($CFG->dataroot . '/pix_plugins/auth/oidc/0')) { + $file->copy_content_to($CFG->dataroot . '/pix_plugins/auth/oidc/0/customicon.jpg'); + theme_reset_all_caches(); + } +} + +/** + * Check for connection abilities. + * + * @param int $userid Moodle user id to check permissions for. + * @param string $mode Mode to check + * 'connect' to check for connect specific capability + * 'disconnect' to check for disconnect capability. + * 'both' to check for disconnect and connect capability. + * @param boolean $require Use require_capability rather than has_capability. + * + * @return boolean True if has capability. + */ +function auth_oidc_connectioncapability($userid, $mode = 'connect', $require = false) { + $check = 'has_capability'; + if ($require) { + // If requiring the capability and user has manageconnection than checking connect and disconnect is not needed. + $check = 'require_capability'; + if (has_capability('auth/oidc:manageconnection', \context_user::instance($userid), $userid)) { + return true; + } + } else if ($check('auth/oidc:manageconnection', \context_user::instance($userid), $userid)) { + return true; + } + + $result = false; + switch ($mode) { + case "connect": + $result = $check('auth/oidc:manageconnectionconnect', \context_user::instance($userid), $userid); + break; + case "disconnect": + $result = $check('auth/oidc:manageconnectiondisconnect', \context_user::instance($userid), $userid); + break; + case "both": + $result = $check('auth/oidc:manageconnectionconnect', \context_user::instance($userid), $userid); + $result = $result && $check('auth/oidc:manageconnectiondisconnect', \context_user::instance($userid), $userid); + } + if ($require) { + return true; + } + + return $result; +} + +/** + * Determine if local_o365 plugins is installed. + * + * @return bool + */ +function auth_oidc_is_local_365_installed() { + global $CFG, $DB; + + $dbmanager = $DB->get_manager(); + + return file_exists($CFG->dirroot . '/local/o365/version.php') && + $DB->record_exists('config_plugins', ['plugin' => 'local_o365', 'name' => 'version']) && + $dbmanager->table_exists('local_o365_objects') && + $dbmanager->table_exists('local_o365_connections'); +} + +/** + * Return details of all auth_oidc tokens having empty Moodle user IDs. + * + * @return array + */ +function auth_oidc_get_tokens_with_empty_ids() { + global $DB; + + $emptyuseridtokens = []; + + $records = $DB->get_records('auth_oidc_token', ['userid' => '0']); + + foreach ($records as $record) { + $item = new stdClass(); + $item->id = $record->id; + $item->oidcusername = $record->oidcusername; + $item->useriditifier = $record->useridentifier; + $item->moodleusername = $record->username; + $item->userid = 0; + $item->oidcuniqueid = $record->oidcuniqid; + $item->matchingstatus = get_string('unmatched', 'auth_oidc'); + $item->details = get_string('na', 'auth_oidc'); + $deletetokenurl = new moodle_url('/auth/oidc/cleanupoidctokens.php', ['id' => $record->id]); + $item->action = html_writer::link($deletetokenurl, get_string('delete_token', 'auth_oidc')); + + $emptyuseridtokens[$record->id] = $item; + } + + return $emptyuseridtokens; +} + +/** + * Return details of all auth_oidc tokens with matching Moodle user IDs, but mismatched usernames. + * + * @return array + */ +function auth_oidc_get_tokens_with_mismatched_usernames() { + global $DB; + + $mismatchedtokens = []; + + $sql = 'SELECT tok.id AS id, tok.userid AS tokenuserid, tok.username AS tokenusername, tok.oidcusername AS oidcusername, + tok.useridentifier, tok.oidcuniqid as oidcuniqid, u.id AS muserid, u.username AS musername + FROM {auth_oidc_token} tok + JOIN {user} u ON u.id = tok.userid + WHERE tok.userid != 0 + AND u.username != tok.username'; + $records = $DB->get_recordset_sql($sql); + foreach ($records as $record) { + $item = new stdClass(); + $item->id = $record->id; + $item->oidcusername = $record->oidcusername; + $item->useridentifier = $record->useridentifier; + $item->userid = $record->muserid; + $item->oidcuniqueid = $record->oidcuniqid; + $item->matchingstatus = get_string('mismatched', 'auth_oidc'); + $item->details = get_string('mismatched_details', 'auth_oidc', + ['tokenusername' => $record->tokenusername, 'moodleusername' => $record->musername]); + $deletetokenurl = new moodle_url('/auth/oidc/cleanupoidctokens.php', ['id' => $record->id]); + $item->action = html_writer::link($deletetokenurl, get_string('delete_token_and_reference', 'auth_oidc')); + + $mismatchedtokens[$record->id] = $item; + } + + return $mismatchedtokens; +} + +/** + * Delete the auth_oidc token with the ID. + * + * @param int $tokenid + */ +function auth_oidc_delete_token(int $tokenid): void { + global $DB; + + if (auth_oidc_is_local_365_installed()) { + $sql = 'SELECT obj.id, obj.objectid, tok.token, u.id AS userid, u.email + FROM {local_o365_objects} obj + JOIN {auth_oidc_token} tok ON obj.o365name = tok.username + JOIN {user} u ON obj.moodleid = u.id + WHERE obj.type = :type AND tok.id = :tokenid'; + if ($objectrecord = $DB->get_record_sql($sql, ['type' => 'user', 'tokenid' => $tokenid], IGNORE_MULTIPLE)) { + // Delete record from local_o365_objects. + $DB->delete_records('local_o365_objects', ['id' => $objectrecord->id]); + + // Delete record from local_o365_token. + $DB->delete_records('local_o365_token', ['user_id' => $objectrecord->userid]); + + // Delete record from local_o365_connections. + $DB->delete_records_select('local_o365_connections', 'muserid = :userid OR LOWER(entraidupn) = :email', + ['userid' => $objectrecord->userid, 'email' => $objectrecord->email]); + } + } + + $DB->delete_records('auth_oidc_token', ['id' => $tokenid]); +} + +/** + * Return the list of remote field options in field mapping. + * + * @return array + */ +function auth_oidc_get_remote_fields() { + if (auth_oidc_is_local_365_installed()) { + $remotefields = [ + '' => get_string('settings_fieldmap_feild_not_mapped', 'auth_oidc'), + 'bindingusernameclaim' => get_string('settings_fieldmap_field_bindingusernameclaim', 'auth_oidc'), + 'objectId' => get_string('settings_fieldmap_field_objectId', 'auth_oidc'), + 'userPrincipalName' => get_string('settings_fieldmap_field_userPrincipalName', 'auth_oidc'), + 'displayName' => get_string('settings_fieldmap_field_displayName', 'auth_oidc'), + 'givenName' => get_string('settings_fieldmap_field_givenName', 'auth_oidc'), + 'surname' => get_string('settings_fieldmap_field_surname', 'auth_oidc'), + 'mail' => get_string('settings_fieldmap_field_mail', 'auth_oidc'), + 'onPremisesSamAccountName' => get_string('settings_fieldmap_field_onPremisesSamAccountName', 'auth_oidc'), + 'streetAddress' => get_string('settings_fieldmap_field_streetAddress', 'auth_oidc'), + 'city' => get_string('settings_fieldmap_field_city', 'auth_oidc'), + 'postalCode' => get_string('settings_fieldmap_field_postalCode', 'auth_oidc'), + 'state' => get_string('settings_fieldmap_field_state', 'auth_oidc'), + 'country' => get_string('settings_fieldmap_field_country', 'auth_oidc'), + 'jobTitle' => get_string('settings_fieldmap_field_jobTitle', 'auth_oidc'), + 'department' => get_string('settings_fieldmap_field_department', 'auth_oidc'), + 'companyName' => get_string('settings_fieldmap_field_companyName', 'auth_oidc'), + 'preferredLanguage' => get_string('settings_fieldmap_field_preferredLanguage', 'auth_oidc'), + 'employeeId' => get_string('settings_fieldmap_field_employeeId', 'auth_oidc'), + 'businessPhones' => get_string('settings_fieldmap_field_businessPhones', 'auth_oidc'), + 'faxNumber' => get_string('settings_fieldmap_field_faxNumber', 'auth_oidc'), + 'mobilePhone' => get_string('settings_fieldmap_field_mobilePhone', 'auth_oidc'), + 'officeLocation' => get_string('settings_fieldmap_field_officeLocation', 'auth_oidc'), + 'preferredName' => get_string('settings_fieldmap_field_preferredName', 'auth_oidc'), + 'manager' => get_string('settings_fieldmap_field_manager', 'auth_oidc'), + 'manager_email' => get_string('settings_fieldmap_field_manager_email', 'auth_oidc'), + 'teams' => get_string('settings_fieldmap_field_teams', 'auth_oidc'), + 'groups' => get_string('settings_fieldmap_field_groups', 'auth_oidc'), + 'roles' => get_string('settings_fieldmap_field_roles', 'auth_oidc'), + ]; + + $order = 0; + while ($order++ < 15) { + $remotefields['extensionAttribute' . $order] = get_string('settings_fieldmap_field_extensionattribute', 'auth_oidc', + $order); + } + + // SDS profile sync. + [$sdsprofilesyncenabled, $schoolid, $schoolname] = local_o365\feature\sds\utils::get_profile_sync_status_with_id_name(); + + if ($sdsprofilesyncenabled) { + $remotefields['sds_school_id'] = get_string('settings_fieldmap_field_sds_school_id', 'auth_oidc', + get_config('local_o365', 'sdsprofilesync', $schoolid)); + $remotefields['sds_school_name'] = get_string('settings_fieldmap_field_sds_school_name', 'auth_oidc', $schoolname); + $remotefields['sds_school_role'] = get_string('settings_fieldmap_field_sds_school_role', 'auth_oidc'); + $remotefields['sds_student_externalId'] = get_string('settings_fieldmap_field_sds_student_externalId', 'auth_oidc'); + $remotefields['sds_student_birthDate'] = get_string('settings_fieldmap_field_sds_student_birthDate', 'auth_oidc'); + $remotefields['sds_student_grade'] = get_string('settings_fieldmap_field_sds_student_grade', 'auth_oidc'); + $remotefields['sds_student_graduationYear'] = get_string('settings_fieldmap_field_sds_student_graduationYear', + 'auth_oidc'); + $remotefields['sds_student_studentNumber'] = get_string('settings_fieldmap_field_sds_student_studentNumber', + 'auth_oidc'); + $remotefields['sds_teacher_externalId'] = get_string('settings_fieldmap_field_sds_teacher_externalId', 'auth_oidc'); + $remotefields['sds_teacher_teacherNumber'] = get_string('settings_fieldmap_field_sds_teacher_teacherNumber', + 'auth_oidc'); + } + } else { + $remotefields = [ + '' => get_string('settings_fieldmap_feild_not_mapped', 'auth_oidc'), + 'bindingusernameclaim' => get_string('settings_fieldmap_field_bindingusernameclaim', 'auth_oidc'), + 'objectId' => get_string('settings_fieldmap_field_objectId', 'auth_oidc'), + 'userPrincipalName' => get_string('settings_fieldmap_field_userPrincipalName', 'auth_oidc'), + 'givenName' => get_string('settings_fieldmap_field_givenName', 'auth_oidc'), + 'surname' => get_string('settings_fieldmap_field_surname', 'auth_oidc'), + 'mail' => get_string('settings_fieldmap_field_mail', 'auth_oidc'), + ]; + } + + return $remotefields; +} + +/** + * Return the list of available remote fields to map email field. + * + * @return array + */ +function auth_oidc_get_email_remote_fields() { + $remotefields = [ + 'mail' => get_string('settings_fieldmap_field_mail', 'auth_oidc'), + 'userPrincipalName' => get_string('settings_fieldmap_field_userPrincipalName', 'auth_oidc'), + ]; + + return $remotefields; +} + +/** + * Return the current field mapping settings in an array. + * + * @return array + */ +function auth_oidc_get_field_mappings() { + $fieldmappings = []; + + $userfields = auth_oidc_get_all_user_fields(); + + $authoidcconfig = get_config('auth_oidc'); + + foreach ($userfields as $userfield) { + $fieldmapsettingname = 'field_map_' . $userfield; + if (property_exists($authoidcconfig, $fieldmapsettingname) && $authoidcconfig->$fieldmapsettingname) { + $fieldsetting = []; + $fieldsetting['field_map'] = $authoidcconfig->$fieldmapsettingname; + + $fieldlocksettingname = 'field_lock_' . $userfield; + if (property_exists($authoidcconfig, $fieldlocksettingname)) { + $fieldsetting['field_lock'] = $authoidcconfig->$fieldlocksettingname; + } else { + $fieldsetting['field_lock'] = 'unlocked'; + } + + $fieldupdatelocksettignname = 'field_updatelocal_' . $userfield; + if (property_exists($authoidcconfig, $fieldupdatelocksettignname)) { + $fieldsetting['update_local'] = $authoidcconfig->$fieldupdatelocksettignname; + } else { + $fieldsetting['update_local'] = 'always'; + } + + $fieldmappings[$userfield] = $fieldsetting; + } + } + + if (!array_key_exists('email', $fieldmappings)) { + $fieldmappings['email'] = auth_oidc_apply_default_email_mapping(); + } + + return $fieldmappings; +} + +/** + * Apply default email mapping settings. + * + * @return array + */ +function auth_oidc_apply_default_email_mapping() { + $existingsetting = get_config('auth_oidc', 'field_map_email'); + if ($existingsetting != 'mail') { + add_to_config_log('field_map_email', $existingsetting, 'mail', 'auth_oidc'); + } + set_config('field_map_email', 'mail', 'auth_oidc'); + + $authoidcconfig = get_config('auth_oidc'); + + $fieldsetting = []; + $fieldsetting['field_map'] = 'mail'; + + if (property_exists($authoidcconfig, 'field_lock_email')) { + $fieldsetting['field_lock'] = $authoidcconfig->field_lock_email; + } else { + $fieldsetting['field_lock'] = 'unlocked'; + } + + if (property_exists($authoidcconfig, 'field_updatelocal_email')) { + $fieldsetting['update_local'] = $authoidcconfig->field_updatelocal_email; + } else { + $fieldsetting['update_local'] = 'always'; + } + + return $fieldsetting; +} + +/** + * Helper function used to print mapping and locking for auth_oidc plugin on admin pages. + * + * @param stdclass $settings Moodle admin settings instance + * @param string $auth authentication plugin shortname + * @param array $userfields user profile fields + * @param string $helptext help text to be displayed at top of form + * @param boolean $mapremotefields Map fields or lock only. + * @param boolean $updateremotefields Allow remote updates + * @param array $customfields list of custom profile fields + */ +function auth_oidc_display_auth_lock_options($settings, $auth, $userfields, $helptext, $mapremotefields, $updateremotefields, + $customfields = []) { + global $DB; + + // Introductory explanation and help text. + if ($mapremotefields) { + $settings->add(new admin_setting_heading($auth.'/data_mapping', new lang_string('auth_data_mapping', 'auth'), $helptext)); + } else { + $settings->add(new admin_setting_heading($auth.'/auth_fieldlocks', new lang_string('auth_fieldlocks', 'auth'), $helptext)); + } + + // Generate the list of options. + $lockoptions = [ + 'unlocked' => get_string('unlocked', 'auth'), + 'unlockedifempty' => get_string('unlockedifempty', 'auth'), + 'locked' => get_string('locked', 'auth'), + ]; + + if (auth_oidc_is_local_365_installed()) { + $alwaystext = get_string('update_oncreate_and_onlogin_and_usersync', 'auth_oidc'); + $onlogintext = get_string('update_onlogin_and_usersync', 'auth_oidc'); + } else { + $alwaystext = get_string('update_oncreate_and_onlogin', 'auth_oidc'); + $onlogintext = get_string('update_onlogin', 'auth'); + } + $updatelocaloptions = [ + 'always' => $alwaystext, + 'oncreate' => get_string('update_oncreate', 'auth'), + 'onlogin' => $onlogintext, + ]; + + $updateextoptions = [ + '0' => get_string('update_never', 'auth'), + '1' => get_string('update_onupdate', 'auth'), + ]; + + // Generate the list of profile fields to allow updates / lock. + if (!empty($customfields)) { + $userfields = array_merge($userfields, $customfields); + $customfieldname = $DB->get_records('user_info_field', null, '', 'shortname, name'); + } + + $remotefields = auth_oidc_get_remote_fields(); + $emailremotefields = auth_oidc_get_email_remote_fields(); + + foreach ($userfields as $field) { + // Define the fieldname we display to the user. + // this includes special handling for some profile fields. + $fieldname = $field; + $fieldnametoolong = false; + if ($fieldname === 'lang') { + $fieldname = get_string('language'); + } else if (!empty($customfields) && in_array($field, $customfields)) { + // If custom field then pick name from database. + $fieldshortname = str_replace('profile_field_', '', $fieldname); + $fieldname = $customfieldname[$fieldshortname]->name; + if (core_text::strlen($fieldshortname) > 67) { + // If custom profile field name is longer than 67 characters we will not be able to store the setting + // such as 'field_updateremote_profile_field_NOTSOSHORTSHORTNAME' in the database because the character + // limit for the setting name is 100. + $fieldnametoolong = true; + } + } else if ($fieldname == 'url') { + $fieldname = get_string('webpage'); + } else { + $fieldname = get_string($fieldname); + } + + // Generate the list of fields / mappings. + if ($fieldnametoolong) { + // Display a message that the field can not be mapped because it's too long. + $url = new moodle_url('/user/profile/index.php'); + $a = (object)['fieldname' => s($fieldname), 'shortname' => s($field), 'charlimit' => 67, 'link' => $url->out()]; + $settings->add(new admin_setting_heading($auth.'/field_not_mapped_'.sha1($field), '', + get_string('cannotmapfield', 'auth', $a))); + } else if ($mapremotefields) { + // We are mapping to a remote field here. + // Mapping. + if ($field == 'email') { + $settings->add(new admin_setting_configselect("auth_oidc/field_map_{$field}", + get_string('auth_fieldmapping', 'auth', $fieldname), '', null, $emailremotefields)); + } else { + $settings->add(new admin_setting_configselect("auth_oidc/field_map_{$field}", + get_string('auth_fieldmapping', 'auth', $fieldname), '', null, $remotefields)); + } + + // Update local. + $settings->add(new admin_setting_configselect("auth_{$auth}/field_updatelocal_{$field}", + get_string('auth_updatelocalfield', 'auth', $fieldname), '', 'always', $updatelocaloptions)); + + // Update remote. + if ($updateremotefields) { + $settings->add(new admin_setting_configselect("auth_{$auth}/field_updateremote_{$field}", + get_string('auth_updateremotefield', 'auth', $fieldname), '', 0, $updateextoptions)); + } + + // Lock fields. + $settings->add(new admin_setting_configselect("auth_{$auth}/field_lock_{$field}", + get_string('auth_fieldlockfield', 'auth', $fieldname), '', 'unlocked', $lockoptions)); + } else { + // Lock fields Only. + $settings->add(new admin_setting_configselect("auth_{$auth}/field_lock_{$field}", + get_string('auth_fieldlockfield', 'auth', $fieldname), '', 'unlocked', $lockoptions)); + } + } +} + +/** + * Return all user profile field names in an array. + * + * @return array|string[]|null + */ +function auth_oidc_get_all_user_fields() { + $authplugin = get_auth_plugin('oidc'); + $userfields = $authplugin->userfields; + $userfields = array_merge($userfields, $authplugin->get_custom_user_profile_fields()); + + return $userfields; +} + +/** + * Determine the endpoint version of the given Microsoft Entra ID / Microsoft authorization or token endpoint. + * + * @param string $endpoint The URL of the endpoint to be checked. + * @return int The version of the Microsoft endpoint (1 or 2) or unknown. + */ +function auth_oidc_determine_endpoint_version(string $endpoint) { + $endpointversion = AUTH_OIDC_MICROSOFT_ENDPOINT_VERSION_UNKNOWN; + + if (strpos($endpoint, 'https://login.microsoftonline.com/') === 0) { + if (strpos($endpoint, 'oauth2/v2.0/') !== false) { + $endpointversion = AUTH_OIDC_MICROSOFT_ENDPOINT_VERSION_2; + } else if (strpos($endpoint, 'oauth2') !== false) { + $endpointversion = AUTH_OIDC_MICROSOFT_ENDPOINT_VERSION_1; + } + } + + return $endpointversion; +} + +/** + * Return formatted form element name to be used by configuration variables in custom forms. + * + * @param string $stringid + * @return string + */ +function auth_oidc_config_name_in_form(string $stringid) { + $formatedformitemname = get_string($stringid, 'auth_oidc') . + html_writer::span('auth_oidc | ' . $stringid, 'form-shortname d-block small text-muted'); + + return $formatedformitemname; +} + +/** + * Check if the auth_oidc plugin has been configured with the minimum settings for the SSO integration to work. + * + * @return bool + */ +function auth_oidc_is_setup_complete() { + $pluginconfig = get_config('auth_oidc'); + if (empty($pluginconfig->clientid) || empty($pluginconfig->idptype) || empty($pluginconfig->clientauthmethod)) { + return false; + } + + switch ($pluginconfig->clientauthmethod) { + case AUTH_OIDC_AUTH_METHOD_SECRET: + if (empty($pluginconfig->clientsecret)) { + return false; + } + break; + case AUTH_OIDC_AUTH_METHOD_CERTIFICATE: + if (!isset($pluginconfig->clientcertsource)) { + $existingclientcertsource = get_config('auth_oidc', 'clientcertsource'); + if ($existingclientcertsource != AUTH_OIDC_AUTH_CERT_SOURCE_TEXT) { + add_to_config_log('clientcertsource', $existingclientcertsource, AUTH_OIDC_AUTH_CERT_SOURCE_TEXT, 'auth_oidc'); + } + set_config('clientcertsource', AUTH_OIDC_AUTH_CERT_SOURCE_TEXT, 'auth_oidc'); + $pluginconfig->clientcertsource = AUTH_OIDC_AUTH_CERT_SOURCE_TEXT; + } + switch ($pluginconfig->clientcertsource) { + case AUTH_OIDC_AUTH_CERT_SOURCE_FILE: + if (!utils::get_certpath() || !utils::get_keypath()) { + return false; + } + break; + case AUTH_OIDC_AUTH_CERT_SOURCE_TEXT: + if (empty($pluginconfig->clientcert) || empty($pluginconfig->clientprivatekey)) { + return false; + } + break; + } + break; + } + + if (empty($pluginconfig->authendpoint) || empty($pluginconfig->tokenendpoint)) { + return false; + } + + return true; +} + +/** + * Return the name of the configured IdP type. + * + * @return lang_string|string + */ +function auth_oidc_get_idp_type_name() { + $idptypename = ''; + + switch (get_config('auth_oidc', 'idptype')) { + case AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID: + $idptypename = get_string('idp_type_microsoft_entra_id', 'auth_oidc'); + break; + case AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM: + $idptypename = get_string('idp_type_microsoft_identity_platform', 'auth_oidc'); + break; + case AUTH_OIDC_IDP_TYPE_OTHER: + $idptypename = get_string('idp_type_other', 'auth_oidc'); + break; + } + + return $idptypename; +} + +/** + * Return the name of the configured authentication method. + * + * @return lang_string|string + */ +function auth_oidc_get_client_auth_method_name() { + $authmethodname = ''; + + switch (get_config('auth_oidc', 'clientauthmethod')) { + case AUTH_OIDC_AUTH_METHOD_SECRET: + $authmethodname = get_string('auth_method_secret', 'auth_oidc'); + break; + case AUTH_OIDC_AUTH_METHOD_CERTIFICATE: + $authmethodname = get_string('auth_method_certificate', 'auth_oidc'); + break; + } + + return $authmethodname; +} + +/** + * Return the name of the configured binding username claim. + * + * @return string + */ +function auth_oidc_get_binding_username_claim(): string { + $bindingusernameclaim = get_config('auth_oidc', 'bindingusernameclaim'); + + if (empty($bindingusernameclaim)) { + $bindingusernameclaim = 'auto'; + } else if ($bindingusernameclaim === 'custom') { + $bindingusernameclaim = get_config('auth_oidc', 'customclaimname'); + } else if (!in_array($bindingusernameclaim, ['auto', 'preferred_username', 'email', 'upn', 'unique_name', 'sub', 'oid', + 'samaccountname'])) { + $bindingusernameclaim = 'auto'; + } + + return $bindingusernameclaim; +} + +/** + * Return the claims that presents in the existing tokens. + * + * @return array + * @throws moodle_exception + */ +function auth_oidc_get_existing_claims(): array { + global $DB; + + $sql = 'SELECT * + FROM {auth_oidc_token} + ORDER BY expiry DESC'; + $tokenrecord = $DB->get_record_sql($sql, null, IGNORE_MULTIPLE); + + $tokenclaims = []; + + if ($tokenrecord) { + $excludedclaims = ['appid', 'appidacr', 'app_displayname', 'ipaddr', 'scp', 'tenant_region_scope', 'ver', 'aud', 'iss', + 'iat', 'nbf', 'exp', 'idtyp', 'plantf', 'xms_tcdt', 'xms_tdbr', 'amr', 'nonce', 'tid', 'acct', 'acr', 'signin_state', + 'wids']; + + foreach (['idtoken', 'token'] as $tokenkey) { + $decodedtoken = jwt::decode($tokenrecord->$tokenkey); + if (is_array($decodedtoken) && count($decodedtoken) > 1) { + foreach ($decodedtoken[1] as $claim => $value) { + if (!in_array($claim, $excludedclaims) && (is_string($value) || is_numeric($value)) && + !in_array($claim, $tokenclaims)) { + $tokenclaims[] = $claim; + } + } + } + } + + asort($tokenclaims); + } + + return $tokenclaims; +} + +/** + * Return if the user sync feature in local_o365 plugin is enabled. + * + * @return bool|void + */ +function auth_oidc_is_user_sync_enabled() { + global $CFG; + + if (auth_oidc_is_local_365_installed()) { + require_once($CFG->dirroot . '/local/o365/classes/feature/usersync/main.php'); + return local_o365\feature\usersync\main::is_enabled(); + } + + return false; +} diff --git a/auth/oidc/logout.php b/auth/oidc/logout.php new file mode 100644 index 00000000000..a16c8097fc9 --- /dev/null +++ b/auth/oidc/logout.php @@ -0,0 +1,50 @@ +. + +/** + * Single Sign Out end point. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +// phpcs:ignore moodle.Files.RequireLogin.Missing +require_once(__DIR__ . '/../../config.php'); + +$PAGE->set_url('/auth/oidc/logout.php'); +$PAGE->set_context(context_system::instance()); + +$sid = optional_param('sid', '', PARAM_TEXT); + +if ($sid) { + if ($authoidcsidrecord = $DB->get_record('auth_oidc_sid', ['sid' => $sid])) { + if ($authoidcsidrecord->userid == $USER->id) { + $authsequence = get_enabled_auth_plugins(); // Auths, in sequence. + foreach ($authsequence as $authname) { + $authplugin = get_auth_plugin($authname); + $authplugin->logoutpage_hook(); + } + + $DB->delete_records('auth_oidc_sid', ['sid' => $sid]); + require_logout(); + redirect($redirect); + } + } +} + +die(); diff --git a/auth/oidc/manageapplication.php b/auth/oidc/manageapplication.php new file mode 100644 index 00000000000..401b8881366 --- /dev/null +++ b/auth/oidc/manageapplication.php @@ -0,0 +1,150 @@ +. + +/** + * OIDC application configuration page. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2022 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +use auth_oidc\form\application; + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->libdir . '/adminlib.php'); +require_once($CFG->dirroot . '/auth/oidc/lib.php'); + +require_login(); + +$url = new moodle_url('/auth/oidc/manageapplication.php'); +$PAGE->set_url($url); +$PAGE->set_context(context_system::instance()); +$PAGE->set_pagelayout('admin'); +$PAGE->set_heading(get_string('settings_page_application', 'auth_oidc')); +$PAGE->set_title(get_string('settings_page_application', 'auth_oidc')); + +$jsparams = [AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM, AUTH_OIDC_AUTH_METHOD_SECRET, AUTH_OIDC_AUTH_METHOD_CERTIFICATE, + get_string('auth_method_certificate', 'auth_oidc')]; +$jsmodule = [ + 'name' => 'auth_oidc', + 'fullpath' => '/auth/oidc/js/module.js', +]; +$PAGE->requires->js_init_call('M.auth_oidc.init', $jsparams, true, $jsmodule); + +admin_externalpage_setup('auth_oidc_application'); + +require_admin(); + +$oidcconfig = get_config('auth_oidc'); + +$form = new application(null, ['oidcconfig' => $oidcconfig]); + +$formdata = []; +foreach (['idptype', 'clientid', 'clientauthmethod', 'clientsecret', 'clientprivatekey', 'clientcert', + 'clientcertsource', 'clientprivatekeyfile', 'clientcertfile', 'clientcertpassphrase', + 'authendpoint', 'tokenendpoint', 'oidcresource', 'oidcscope', 'secretexpiryrecipients', + 'bindingusernameclaim', 'customclaimname'] as $field) { + if (isset($oidcconfig->$field)) { + $formdata[$field] = $oidcconfig->$field; + } +} + +$form->set_data($formdata); + +if ($form->is_cancelled()) { + redirect($url); +} else if ($fromform = $form->get_data()) { + // Handle odd cases where clientauthmethod is not received. + if (!isset($fromform->clientauthmethod)) { + $fromform->clientauthmethod = optional_param('clientauthmethod', AUTH_OIDC_AUTH_METHOD_SECRET, PARAM_INT); + } + + // Prepare config settings to save. + $configstosave = ['idptype', 'clientid', 'clientauthmethod', 'authendpoint', 'tokenendpoint', + 'oidcresource', 'oidcscope']; + + // Depending on the value of clientauthmethod, save clientsecret or (clientprivatekey and clientcert). + switch ($fromform->clientauthmethod) { + case AUTH_OIDC_AUTH_METHOD_SECRET: + $configstosave[] = 'clientsecret'; + $configstosave[] = 'secretexpiryrecipients'; + break; + case AUTH_OIDC_AUTH_METHOD_CERTIFICATE: + $configstosave[] = 'clientcertsource'; + $configstosave[] = 'clientcertpassphrase'; + switch ($fromform->clientcertsource) { + case AUTH_OIDC_AUTH_CERT_SOURCE_TEXT: + $configstosave[] = 'clientprivatekey'; + $configstosave[] = 'clientcert'; + break; + case AUTH_OIDC_AUTH_CERT_SOURCE_FILE: + $configstosave[] = 'clientprivatekeyfile'; + $configstosave[] = 'clientcertfile'; + break; + } + break; + } + + // Save config settings. + $updateapplicationtokenrequired = false; + $settingschanged = false; + foreach ($configstosave as $config) { + $existingsetting = get_config('auth_oidc', $config); + if ($fromform->$config != $existingsetting) { + add_to_config_log($config, $existingsetting, $fromform->$config, 'auth_oidc'); + set_config($config, $fromform->$config, 'auth_oidc'); + $settingschanged = true; + if ($config != 'secretexpiryrecipients') { + $updateapplicationtokenrequired = true; + } + } + } + + // Redirect destination and message depend on IdP type. + $isgraphapiconnected = false; + if ($fromform->idptype != AUTH_OIDC_IDP_TYPE_OTHER) { + if (auth_oidc_is_local_365_installed()) { + $isgraphapiconnected = true; + } + } + + if ($updateapplicationtokenrequired) { + if ($isgraphapiconnected) { + // First, delete the existing application token and purge cache. + unset_config('apptokens', 'local_o365'); + unset_config('azuresetupresult', 'local_o365'); + purge_all_caches(); + + // Then show the message to the user with instructions to update the application token. + $localo365configurl = new moodle_url('/admin/settings.php', ['section' => 'local_o365']); + redirect($localo365configurl, get_string('application_updated_microsoft', 'auth_oidc')); + } else { + redirect($url, get_string('application_updated', 'auth_oidc')); + } + } else if ($settingschanged) { + redirect($url, get_string('application_updated', 'auth_oidc')); + } else { + redirect($url, get_string('application_not_changed', 'auth_oidc')); + } +} + +echo $OUTPUT->header(); + +$form->display(); + +echo $OUTPUT->footer(); diff --git a/auth/oidc/pix/o365.png b/auth/oidc/pix/o365.png new file mode 100644 index 0000000000000000000000000000000000000000..0fa022c25ea9ba897202f1fb751f64b8b9529227 GIT binary patch literal 497 zcmVB?)x<+801dA@%ak@$jjB)&yV2q%8!~BFqC` zz&qW#8SMlJf6p&Y&LfUxAuJ?-NEP!l@Cdk7dM%~GDHX<(6%!+`%DxJ-70^3iTVcr& zhTJ3CMcJqqy)yAK&`}N#i`H`3cdX`zD<<{0zExDHH=|p1-8JOYA8CLa;071~g(nr( zR2cj#xcsD5Sap&BA-AP*Q33>IU-;9!F6@7Iq$ z=Si2s9FPS1eeEI;s$zR~Ga9W4u2eQABM3=>86fIwKLC3`IuvHN=RofRgUbhuOOx}; ngjEs(S7O=eJ>d*h>!1As!hwLQcNwtj00000NkvXXu0mjfzc$O& literal 0 HcmV?d00001 diff --git a/auth/oidc/settings.php b/auth/oidc/settings.php new file mode 100644 index 00000000000..121db834608 --- /dev/null +++ b/auth/oidc/settings.php @@ -0,0 +1,258 @@ +. + +/** + * Plugin settings. + * + * @package auth_oidc + * @author James McQuillan + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +defined('MOODLE_INTERNAL') || die(); + +use auth_oidc\adminsetting\auth_oidc_admin_setting_iconselect; +use auth_oidc\adminsetting\auth_oidc_admin_setting_loginflow; +use auth_oidc\adminsetting\auth_oidc_admin_setting_redirecturi; +use auth_oidc\utils; + +require_once($CFG->dirroot . '/auth/oidc/lib.php'); + +if ($hassiteconfig) { + // Add folder for OIDC settings. + $oidcfolder = new admin_category('oidcfolder', get_string('pluginname', 'auth_oidc')); + $ADMIN->add('authsettings', $oidcfolder); + + // Application configuration page. + $ADMIN->add('oidcfolder', new admin_externalpage('auth_oidc_application', get_string('settings_page_application', 'auth_oidc'), + new moodle_url('/auth/oidc/manageapplication.php'))); + + + $idptype = get_config('auth_oidc', 'idptype'); + if ($idptype) { + // Binding username claim page. + $ADMIN->add('oidcfolder', new admin_externalpage('auth_oidc_binding_username_claim', + get_string('settings_page_binding_username_claim', 'auth_oidc'), + new moodle_url('/auth/oidc/binding_username_claim.php'))); + + // Change binding username claim tool page. + $ADMIN->add('oidcfolder', new admin_externalpage('auth_oidc_change_binding_username_claim_tool', + get_string('settings_page_change_binding_username_claim_tool', 'auth_oidc'), + new moodle_url('/auth/oidc/change_binding_username_claim_tool.php'))); + } + + + // Other settings page and its settings. + $settings = new admin_settingpage($section, get_string('settings_page_other_settings', 'auth_oidc')); + + // Basic heading. + $settings->add(new admin_setting_heading('auth_oidc/basic_heading', get_string('heading_basic', 'auth_oidc'), + get_string('heading_basic_desc', 'auth_oidc'))); + + // Redirect URI. + $settings->add(new auth_oidc_admin_setting_redirecturi('auth_oidc/redirecturi', + get_string('cfg_redirecturi_key', 'auth_oidc'), get_string('cfg_redirecturi_desc', 'auth_oidc'), utils::get_redirecturl())); + + // Link to authentication options. + $authenticationconfigurationurl = new moodle_url('/auth/oidc/manageapplication.php'); + $settings->add(new admin_setting_description('auth_oidc/authenticationlink', + get_string('settings_page_application', 'auth_oidc'), + get_string('cfg_authenticationlink_desc', 'auth_oidc', $authenticationconfigurationurl->out()))); + + // Additional options heading. + $settings->add(new admin_setting_heading('auth_oidc/additional_options_heading', + get_string('heading_additional_options', 'auth_oidc'), get_string('heading_additional_options_desc', 'auth_oidc'))); + + // Force redirect. + $settings->add(new admin_setting_configcheckbox('auth_oidc/forceredirect', + get_string('cfg_forceredirect_key', 'auth_oidc'), get_string('cfg_forceredirect_desc', 'auth_oidc'), 0)); + + // Silent login mode. + $forceloginconfigurl = new moodle_url('/admin/settings.php', ['section' => 'sitepolicies']); + $settings->add(new admin_setting_configcheckbox('auth_oidc/silentloginmode', + get_string('cfg_silentloginmode_key', 'auth_oidc'), + get_string('cfg_silentloginmode_desc', 'auth_oidc', $forceloginconfigurl->out(false)), 0)); + + // Auto-append. + $settings->add(new admin_setting_configtext('auth_oidc/autoappend', + get_string('cfg_autoappend_key', 'auth_oidc'), get_string('cfg_autoappend_desc', 'auth_oidc'), '', PARAM_TEXT)); + + // Domain hint. + $settings->add(new admin_setting_configtext('auth_oidc/domainhint', + get_string('cfg_domainhint_key', 'auth_oidc'), get_string('cfg_domainhint_desc', 'auth_oidc'), '' , PARAM_TEXT)); + + // Login flow. + $settings->add(new auth_oidc_admin_setting_loginflow('auth_oidc/loginflow', + get_string('cfg_loginflow_key', 'auth_oidc'), '', 'authcode')); + + // User restrictions heading. + $settings->add(new admin_setting_heading('auth_oidc/user_restrictions_heading', + get_string('heading_user_restrictions', 'auth_oidc'), get_string('heading_user_restrictions_desc', 'auth_oidc'))); + + // User restrictions. + $settings->add(new admin_setting_configtextarea('auth_oidc/userrestrictions', + get_string('cfg_userrestrictions_key', 'auth_oidc'), get_string('cfg_userrestrictions_desc', 'auth_oidc'), '', PARAM_TEXT)); + + // User restrictions case sensitivity. + $settings->add(new admin_setting_configcheckbox('auth_oidc/userrestrictionscasesensitive', + get_string('cfg_userrestrictionscasesensitive_key', 'auth_oidc'), + get_string('cfg_userrestrictionscasesensitive_desc', 'auth_oidc'), '1')); + + // Sign out integration heading. + $settings->add(new admin_setting_heading('auth_oidc/sign_out_heading', + get_string('heading_sign_out', 'auth_oidc'), get_string('heading_sign_out_desc', 'auth_oidc'))); + + // Single sign out from Moodle to IdP. + $settings->add(new admin_setting_configcheckbox('auth_oidc/single_sign_off', + get_string('cfg_signoffintegration_key', 'auth_oidc'), + get_string('cfg_signoffintegration_desc', 'auth_oidc', $CFG->wwwroot), '0')); + + // IdP logout endpoint. + $settings->add(new admin_setting_configtext('auth_oidc/logouturi', + get_string('cfg_logoutendpoint_key', 'auth_oidc'), get_string('cfg_logoutendpoint_desc', 'auth_oidc'), + 'https://login.microsoftonline.com/organizations/oauth2/logout', PARAM_URL)); + + // Front channel logout URL. + $settings->add(new auth_oidc_admin_setting_redirecturi('auth_oidc/logoutendpoint', + get_string('cfg_frontchannellogouturl_key', 'auth_oidc'), get_string('cfg_frontchannellogouturl_desc', 'auth_oidc'), + utils::get_frontchannellogouturl())); + + // Display heading. + $settings->add(new admin_setting_heading('auth_oidc/display_heading', + get_string('heading_display', 'auth_oidc'), get_string('heading_display_desc', 'auth_oidc'))); + + // Provider Name (opname). + $settings->add(new admin_setting_configtext('auth_oidc/opname', + get_string('cfg_opname_key', 'auth_oidc'), get_string('cfg_opname_desc', 'auth_oidc'), + get_string('pluginname', 'auth_oidc'), PARAM_TEXT)); + + // Icon. + $icons = [ + [ + 'pix' => 'o365', + 'alt' => new lang_string('cfg_iconalt_o365', 'auth_oidc'), + 'component' => 'auth_oidc', + ], + [ + 'pix' => 't/locked', + 'alt' => new lang_string('cfg_iconalt_locked', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 't/lock', + 'alt' => new lang_string('cfg_iconalt_lock', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 't/go', + 'alt' => new lang_string('cfg_iconalt_go', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 't/stop', + 'alt' => new lang_string('cfg_iconalt_stop', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 't/user', + 'alt' => new lang_string('cfg_iconalt_user', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 'u/user35', + 'alt' => new lang_string('cfg_iconalt_user2', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 'i/permissions', + 'alt' => new lang_string('cfg_iconalt_key', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 'i/cohort', + 'alt' => new lang_string('cfg_iconalt_group', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 'i/groups', + 'alt' => new lang_string('cfg_iconalt_group2', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 'i/mnethost', + 'alt' => new lang_string('cfg_iconalt_mnet', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 'i/permissionlock', + 'alt' => new lang_string('cfg_iconalt_userlock', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 't/more', + 'alt' => new lang_string('cfg_iconalt_plus', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 't/approve', + 'alt' => new lang_string('cfg_iconalt_check', 'auth_oidc'), + 'component' => 'moodle', + ], + [ + 'pix' => 't/right', + 'alt' => new lang_string('cfg_iconalt_rightarrow', 'auth_oidc'), + 'component' => 'moodle', + ], + ]; + $settings->add(new auth_oidc_admin_setting_iconselect('auth_oidc/icon', + get_string('cfg_icon_key', 'auth_oidc'), get_string('cfg_icon_desc', 'auth_oidc'), 'auth_oidc:o365', $icons)); + + // Custom icon. + $configkey = new lang_string('cfg_customicon_key', 'auth_oidc'); + $configdesc = new lang_string('cfg_customicon_desc', 'auth_oidc'); + $customiconsetting = new admin_setting_configstoredfile('auth_oidc/customicon', + get_string('cfg_customicon_key', 'auth_oidc'), get_string('cfg_customicon_desc', 'auth_oidc'), 'customicon', 0, + ['accepted_types' => ['.png', '.jpg', '.ico'], 'maxbytes' => get_max_upload_file_size()]); + $customiconsetting->set_updatedcallback('auth_oidc_initialize_customicon'); + $settings->add($customiconsetting); + + // Debugging heading. + $settings->add(new admin_setting_heading('auth_oidc/debugging_heading', + get_string('heading_debugging', 'auth_oidc'), get_string('heading_debugging_desc', 'auth_oidc'))); + + // Record debugging messages. + $settings->add(new admin_setting_configcheckbox('auth_oidc/debugmode', + get_string('cfg_debugmode_key', 'auth_oidc'), get_string('cfg_debugmode_desc', 'auth_oidc'), '0')); + + $ADMIN->add('oidcfolder', $settings); + + // Cleanup OIDC tokens page. + $ADMIN->add('oidcfolder', new admin_externalpage('auth_oidc_cleanup_oidc_tokens', + get_string('settings_page_cleanup_oidc_tokens', 'auth_oidc'), new moodle_url('/auth/oidc/cleanupoidctokens.php'))); + + // Other settings page and its settings. + $fieldmappingspage = new admin_settingpage('auth_oidc_field_mapping', get_string('settings_page_field_mapping', 'auth_oidc')); + $ADMIN->add('oidcfolder', $fieldmappingspage); + + // Display locking / mapping of profile fields. + $authplugin = get_auth_plugin('oidc'); + auth_oidc_display_auth_lock_options($fieldmappingspage, $authplugin->authtype, $authplugin->userfields, + get_string('cfg_field_mapping_desc', 'auth_oidc'), true, false, $authplugin->get_custom_user_profile_fields()); +} + +$settings = null; diff --git a/auth/oidc/styles.css b/auth/oidc/styles.css new file mode 100644 index 00000000000..077b53e3176 --- /dev/null +++ b/auth/oidc/styles.css @@ -0,0 +1,49 @@ +.auth_oidc_ucp_indicator h4 { + display: inline-block; + margin-right: 0.5rem; +} + +.auth_oidc_ucp_indicator h5 { + display: inline-block; + margin-left: 0.5rem; +} + +.auth_oidc_ucp_indicator h5 + span { + display: block; +} + +.cert_textarea textarea { + font-family: 'Courier New', Courier, monospace; +} + +.path-admin-auth-oidc .warning_header { + border: 1px solid #f00; + background-color: #ffe5e5; + padding: 10px; + margin: 10px 0; + color: #f00; + font-weight: bold; +} + +.path-admin-auth-oidc .warning { + color: #f00; + font-weight: bold; +} + +.path-admin-auth-oidc .existing_claims { + display: inline-block; + background-color: #f0f0f0; + border: 1px solid #ccc; + padding: 10px; + font-family: 'Courier New', Courier, monospace; + white-space: pre; +} + +.path-admin-auth-oidc .not_support_user_sync { + color: #f00; +} + +.path-admin-auth-oidc .code { + font-family: 'Courier New', Courier, monospace; + white-space: pre; +} diff --git a/auth/oidc/tests/jwt_test.php b/auth/oidc/tests/jwt_test.php new file mode 100644 index 00000000000..2eb99095b6e --- /dev/null +++ b/auth/oidc/tests/jwt_test.php @@ -0,0 +1,132 @@ +. + +/** + * JWT test cases. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +/** + * Tests jwt. + * + * @group auth_oidc + * @group office365 + */ +final class jwt_test extends \advanced_testcase { + /** + * Perform setup before every test. This tells Moodle's phpunit to reset the database after every test. + */ + protected function setUp(): void { + parent::setUp(); + $this->resetAfterTest(true); + } + + /** + * Dataprovider for test_decode. + * + * @return array Array of arrays of test parameters. + */ + public static function dataprovider_decode(): array { + $tests = []; + + $tests['emptytest'] = [ + '', '', ['Exception', 'Empty or non-string JWT received.'], + ]; + + $tests['nonstringtest'] = [ + 100, '', ['Exception', 'Empty or non-string JWT received.'], + ]; + + $tests['malformed1'] = [ + 'a', '', ['Exception', 'Malformed JWT received.'], + ]; + + $tests['malformed2'] = [ + 'a.b', '', ['Exception', 'Malformed JWT received.'], + ]; + + $tests['malformed3'] = [ + 'a.b.c.d', '', ['Exception', 'Malformed JWT received.'], + ]; + + $tests['badheader1'] = [ + 'h.p.s', '', ['Exception', 'Could not read JWT header'], + ]; + + $header = base64_encode(json_encode(['key' => 'val'])); + $tests['invalidheader1'] = [ + $header . '.p.s', '', ['Exception', 'Invalid JWT header'], + ]; + + $header = base64_encode(json_encode(['alg' => 'ROT13'])); + $tests['badalg1'] = [ + $header . '.p.s', '', ['Exception', 'JWS Alg or JWE not supported'], + ]; + + $header = base64_encode(json_encode(['alg' => 'RS256'])); + $payload = 'p'; + $tests['badpayload1'] = [ + $header . '.' . $payload . '.s', '', ['Exception', 'Could not read JWT payload.'], + ]; + + $header = base64_encode(json_encode(['alg' => 'RS256'])); + $payload = base64_encode('nothing'); + $tests['badpayload2'] = [ + $header . '.' . $payload . '.s', '', ['Exception', 'Could not read JWT payload.'], + ]; + + $header = ['alg' => 'RS256']; + $payload = ['payload' => 'found']; + $headerenc = base64_encode(json_encode($header)); + $payloadenc = base64_encode(json_encode($payload)); + $expected = [$header, $payload]; + $tests['goodpayload1'] = [ + $headerenc . '.' . $payloadenc . '.s', $expected, [], + ]; + + return $tests; + } + + /** + * Test decode. + * + * @dataProvider dataprovider_decode + * @covers \auth_oidc\jwt::decode + * + * @param string $encodedjwt The JWT token to be decoded. + * @param mixed $expectedresult The expected result after decoding. + * @param array $expectedexception The expected exception class and message if an error occurs. + * @return void + */ + public function test_decode($encodedjwt, $expectedresult, $expectedexception): void { + if (!empty($expectedexception)) { + $this->expectException($expectedexception[0]); + $this->expectExceptionMessage($expectedexception[1]); + } + $actualresult = \auth_oidc\jwt::decode($encodedjwt); + $this->assertEquals($expectedresult, $actualresult); + } +} diff --git a/auth/oidc/tests/oidcclient_test.php b/auth/oidc/tests/oidcclient_test.php new file mode 100644 index 00000000000..47e2a41182f --- /dev/null +++ b/auth/oidc/tests/oidcclient_test.php @@ -0,0 +1,132 @@ +. + +/** + * OIDC client test cases. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + + + +namespace auth_oidc; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +/** + * Tests oidcclient. + * + * @group auth_oidc + * @group office365 + */ +final class oidcclient_test extends \advanced_testcase { + /** + * Perform setup before every test. This tells Moodle's phpunit to reset the database after every test. + */ + protected function setUp(): void { + parent::setUp(); + $this->resetAfterTest(true); + } + + /** + * Test getting and setting credentials. + * + * @covers \auth_oidc\tests\mockoidcclient::setcreds + */ + public function test_creds_getters_and_setters(): void { + $httpclient = new \auth_oidc\tests\mockhttpclient(); + $client = new \auth_oidc\tests\mockoidcclient($httpclient); + + $this->assertNull($client->get_clientid()); + $this->assertNull($client->get_clientsecret()); + $this->assertNull($client->get_redirecturi()); + + $id = 'id'; + $secret = 'secret'; + $redirecturi = 'redirecturi'; + $tokenresource = 'resource'; + $scope = (isset($this->config->oidcscope)) ? $this->config->oidcscope : null; + $client->setcreds($id, $secret, $redirecturi, $tokenresource, $scope); + + $this->assertEquals($id, $client->get_clientid()); + $this->assertEquals($secret, $client->get_clientsecret()); + $this->assertEquals($redirecturi, $client->get_redirecturi()); + $this->assertEquals($tokenresource, $client->get_tokenresource()); + } + + /** + * Dataprovider returning endpoints. + * + * @return array Array of arrays of test parameters. + */ + public static function dataprovider_endpoints(): array { + $tests = []; + + $tests['oneinvalid'] = [ + ['auth' => 100], + ['Exception', 'Invalid Endpoint URI received.'], + ]; + + $tests['oneinvalidonevalid1'] = [ + ['auth' => 100, 'token' => 'http://example.com/token'], + ['Exception', 'Invalid Endpoint URI received.'], + ]; + + $tests['oneinvalidonevalid2'] = [ + ['token' => 'http://example.com/token', 'auth' => 100], + ['Exception', 'Invalid Endpoint URI received.'], + ]; + + $tests['onevalid'] = [ + ['token' => 'http://example.com/token'], + [], + ]; + + $tests['twovalid'] = [ + ['auth' => 'http://example.com/auth', 'token' => 'http://example.com/token'], + [], + ]; + + return $tests; + } + + /** + * Test setting and getting endpoints. + * + * @dataProvider dataprovider_endpoints + * @covers \auth_oidc\tests\mockoidcclient::setendpoints + * @param array $endpoints + * @param array $expectedexception + */ + public function test_endpoints_getters_and_setters(array $endpoints, array $expectedexception): void { + if (!empty($expectedexception)) { + $this->expectException($expectedexception[0]); + $this->expectExceptionMessage($expectedexception[1]); + } + $httpclient = new \auth_oidc\tests\mockhttpclient(); + $client = new \auth_oidc\tests\mockoidcclient($httpclient); + $client->setendpoints($endpoints); + + foreach ($endpoints as $type => $uri) { + $this->assertEquals($uri, $client->get_endpoint($type)); + } + } +} diff --git a/auth/oidc/tests/privacy_provider_test.php b/auth/oidc/tests/privacy_provider_test.php new file mode 100644 index 00000000000..6a3989c2dc2 --- /dev/null +++ b/auth/oidc/tests/privacy_provider_test.php @@ -0,0 +1,314 @@ +. + +/** + * Privacy test for auth_oidc + * + * @package auth_oidc + * @author Remote-Learner.net Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2019 Remote Learner.net Inc http://www.remote-learner.net + */ + +namespace auth_oidc; + +use auth_oidc\privacy\provider; + +/** + * Privacy test for auth_oidc + * + * @group auth_oidc + * @group auth_oidc_privacy + * @group office365 + * @group office365_privacy + */ +final class privacy_provider_test extends \core_privacy\tests\provider_testcase { + + /** + * Tests set up. + */ + public function setUp(): void { + parent::setUp(); + $this->resetAfterTest(); + $this->setAdminUser(); + } + + /** + * Check that a user context is returned if there is any user data for this user. + * + * @covers \auth_oidc\privacy\provider::get_contexts_for_userid + */ + public function test_get_contexts_for_userid(): void { + $user = $this->getDataGenerator()->create_user(); + $this->assertEmpty(provider::get_contexts_for_userid($user->id)); + + // Create user records. + self::create_token($user->id); + self::create_prevlogin($user->id); + + $contextlist = provider::get_contexts_for_userid($user->id); + // Check that we only get back one context. + $this->assertCount(1, $contextlist); + + // Check that a context is returned and is the expected context. + $usercontext = \context_user::instance($user->id); + $this->assertEquals($usercontext->id, $contextlist->get_contextids()[0]); + } + + /** + * Test that only users with a user context are fetched. + * + * @covers \auth_oidc\privacy\provider::get_users_in_context + */ + public function test_get_users_in_context(): void { + $this->resetAfterTest(); + + $component = 'auth_oidc'; + // Create a user. + $user = $this->getDataGenerator()->create_user(); + $usercontext = \context_user::instance($user->id); + + // The list of users should not return anything yet (related data still haven't been created). + $userlist = new \core_privacy\local\request\userlist($usercontext, $component); + provider::get_users_in_context($userlist); + $this->assertCount(0, $userlist); + + // Create user records. + self::create_token($user->id); + self::create_prevlogin($user->id); + + // The list of users for user context should return the user. + provider::get_users_in_context($userlist); + $this->assertCount(1, $userlist); + $expected = [$user->id]; + $actual = $userlist->get_userids(); + $this->assertEquals($expected, $actual); + + // The list of users for system context should not return any users. + $userlist = new \core_privacy\local\request\userlist(\context_system::instance(), $component); + provider::get_users_in_context($userlist); + $this->assertCount(0, $userlist); + } + + /** + * Test that user data is exported correctly. + * + * @covers \auth_oidc\privacy\provider::export_user_data + */ + public function test_export_user_data(): void { + // Create a user record. + $user = $this->getDataGenerator()->create_user(); + $tokenrecord = self::create_token($user->id); + $prevloginrecord = self::create_prevlogin($user->id); + + $usercontext = \context_user::instance($user->id); + + $writer = \core_privacy\local\request\writer::with_context($usercontext); + $this->assertFalse($writer->has_any_data()); + $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'auth_oidc', [$usercontext->id]); + provider::export_user_data($approvedlist); + // Token. + $data = $writer->get_data([ + get_string('privacy:metadata:auth_oidc', 'auth_oidc'), + get_string('privacy:metadata:auth_oidc_token', 'auth_oidc'), + ]); + $this->assertEquals($tokenrecord->userid, $data->userid); + $this->assertEquals($tokenrecord->token, $data->token); + // Previous login. + $data = $writer->get_data([ + get_string('privacy:metadata:auth_oidc', 'auth_oidc'), + get_string('privacy:metadata:auth_oidc_prevlogin', 'auth_oidc'), + ]); + $this->assertEquals($prevloginrecord->userid, $data->userid); + $this->assertEquals($prevloginrecord->method, $data->method); + $this->assertEquals($prevloginrecord->password, $data->password); + } + + /** + * Test deleting all user data for a specific context. + * + * @covers \auth_oidc\privacy\provider::delete_data_for_all_users_in_context + */ + public function test_delete_data_for_all_users_in_context(): void { + global $DB; + + // Create a user record. + $user1 = $this->getDataGenerator()->create_user(); + self::create_token($user1->id); + self::create_prevlogin($user1->id); + $user1context = \context_user::instance($user1->id); + + $user2 = $this->getDataGenerator()->create_user(); + self::create_token($user2->id); + self::create_prevlogin($user2->id); + + // Get all accounts. There should be two. + $this->assertCount(2, $DB->get_records('auth_oidc_token', [])); + $this->assertCount(2, $DB->get_records('auth_oidc_prevlogin', [])); + + // Delete everything for the first user context. + provider::delete_data_for_all_users_in_context($user1context); + + $this->assertCount(0, $DB->get_records('auth_oidc_token', ['userid' => $user1->id])); + $this->assertCount(0, $DB->get_records('auth_oidc_prevlogin', ['userid' => $user1->id])); + + // Get all accounts. There should be one. + $this->assertCount(1, $DB->get_records('auth_oidc_token', [])); + $this->assertCount(1, $DB->get_records('auth_oidc_prevlogin', [])); + } + + /** + * This should work identical to the above test. + * + * @covers \auth_oidc\privacy\provider::delete_data_for_user + */ + public function test_delete_data_for_user(): void { + global $DB; + + // Create a user record. + $user1 = $this->getDataGenerator()->create_user(); + self::create_token($user1->id); + self::create_prevlogin($user1->id); + $user1context = \context_user::instance($user1->id); + + $user2 = $this->getDataGenerator()->create_user(); + self::create_token($user2->id); + self::create_prevlogin($user2->id); + + // Get all accounts. There should be two. + $this->assertCount(2, $DB->get_records('auth_oidc_token', [])); + $this->assertCount(2, $DB->get_records('auth_oidc_prevlogin', [])); + + // Delete everything for the first user. + $approvedlist = new \core_privacy\local\request\approved_contextlist($user1, 'auth_oidc', [$user1context->id]); + provider::delete_data_for_user($approvedlist); + + $this->assertCount(0, $DB->get_records('auth_oidc_token', ['userid' => $user1->id])); + $this->assertCount(0, $DB->get_records('auth_oidc_prevlogin', ['userid' => $user1->id])); + + // Get all accounts. There should be one. + $this->assertCount(1, $DB->get_records('auth_oidc_token', [])); + $this->assertCount(1, $DB->get_records('auth_oidc_prevlogin', [])); + } + + /** + * Test that data for users in approved userlist is deleted. + * + * @covers \auth_oidc\privacy\provider::delete_data_for_users + */ + public function test_delete_data_for_users(): void { + $this->resetAfterTest(); + + $component = 'auth_oidc'; + // Create user1. + $user1 = $this->getDataGenerator()->create_user(); + $usercontext1 = \context_user::instance($user1->id); + self::create_token($user1->id); + self::create_prevlogin($user1->id); + + // Create user2. + $user2 = $this->getDataGenerator()->create_user(); + $usercontext2 = \context_user::instance($user2->id); + self::create_token($user2->id); + self::create_prevlogin($user2->id); + + // The list of users for usercontext1 should return user1. + $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); + provider::get_users_in_context($userlist1); + $this->assertCount(1, $userlist1); + $expected = [$user1->id]; + $actual = $userlist1->get_userids(); + $this->assertEquals($expected, $actual); + + // The list of users for usercontext2 should return user2. + $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); + provider::get_users_in_context($userlist2); + $this->assertCount(1, $userlist2); + $expected = [$user2->id]; + $actual = $userlist2->get_userids(); + $this->assertEquals($expected, $actual); + + // Add userlist1 to the approved user list. + $approvedlist = new \core_privacy\local\request\approved_userlist($usercontext1, $component, $userlist1->get_userids()); + + // Delete user data using delete_data_for_user for usercontext1. + provider::delete_data_for_users($approvedlist); + + // Re-fetch users in usercontext1 - The user list should now be empty. + $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); + provider::get_users_in_context($userlist1); + $this->assertCount(0, $userlist1); + // Re-fetch users in usercontext2 - The user list should not be empty (user2). + $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); + provider::get_users_in_context($userlist2); + $this->assertCount(1, $userlist2); + + // User data should be only removed in the user context. + $systemcontext = \context_system::instance(); + // Add userlist2 to the approved user list in the system context. + $approvedlist = new \core_privacy\local\request\approved_userlist($systemcontext, $component, $userlist2->get_userids()); + // Delete user1 data using delete_data_for_user. + provider::delete_data_for_users($approvedlist); + // Re-fetch users in usercontext2 - The user list should not be empty (user2). + $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); + provider::get_users_in_context($userlist2); + $this->assertCount(1, $userlist2); + } + + /** + * Create a token record for the specified userid. + * + * @param int $userid + * @return \stdClass + * @throws \dml_exception + */ + private static function create_token(int $userid): \stdClass { + global $DB; + $record = new \stdClass(); + $record->oidcuniqid = "user@example.com"; + $record->username = "user@example.com"; + $record->userid = $userid; + $record->oidcusername = "user@example.com"; + $record->useridentifier = "user@example.com"; + $record->scope = "All"; + $record->tokenresource = "https://graph.microsoft.com"; + $record->authcode = "authcode123"; + $record->token = "token123"; + $record->expiry = 12345; + $record->refreshtoken = "refresh123"; + $record->idtoken = "idtoken123"; + $record->id = $DB->insert_record('auth_oidc_token', $record); + return $record; + } + + /** + * Create a previous login record for the specified userid. + * + * @param int $userid + * @return \stdClass + * @throws \dml_exception + */ + private static function create_prevlogin(int $userid): \stdClass { + global $DB; + $record = new \stdClass(); + $record->userid = $userid; + $record->method = "manual"; + $record->password = "abc123"; + $record->id = $DB->insert_record('auth_oidc_prevlogin', $record); + return $record; + } + +} diff --git a/auth/oidc/ucp.php b/auth/oidc/ucp.php new file mode 100644 index 00000000000..5d5d701e1fc --- /dev/null +++ b/auth/oidc/ucp.php @@ -0,0 +1,110 @@ +. + +/** + * User control panel page. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +require_once(__DIR__.'/../../config.php'); +require_once(__DIR__.'/auth.php'); +require_once(__DIR__.'/lib.php'); + +require_login(); + +$action = optional_param('action', null, PARAM_TEXT); + +$oidctoken = $DB->get_record('auth_oidc_token', ['userid' => $USER->id]); +$oidcconnected = (!empty($oidctoken)) ? true : false; + +$oidcloginconnected = ($USER->auth === 'oidc') ? true : false; + +if (!is_enabled_auth('oidc')) { + throw new moodle_exception('erroroidcnotenabled', 'auth_oidc'); +} + +if (!empty($action)) { + if ($action === 'connectlogin' && $oidcloginconnected === false) { + // Use authorization request login flow to connect existing users. + auth_oidc_connectioncapability($USER->id, 'connect', true); + $auth = new \auth_oidc\loginflow\authcode; + $auth->set_httpclient(new \auth_oidc\httpclient()); + $auth->initiateauthrequest(); + } else if ($action === 'disconnectlogin' && $oidcloginconnected === true) { + if (is_enabled_auth('manual') === true) { + auth_oidc_connectioncapability($USER->id, 'disconnect', true); + $auth = new \auth_plugin_oidc; + $auth->set_httpclient(new \auth_oidc\httpclient()); + $auth->disconnect(); + } + } else { + throw new moodle_exception('errorucpinvalidaction', 'auth_oidc'); + } +} else { + $PAGE->set_url('/auth/oidc/ucp.php'); + $usercontext = \context_user::instance($USER->id); + $PAGE->set_context(\context_system::instance()); + $PAGE->set_pagelayout('standard'); + $USER->editing = false; + $authconfig = get_config('auth_oidc'); + $opname = (!empty($authconfig->opname)) ? $authconfig->opname : get_string('pluginname', 'auth_oidc'); + + $ucptitle = get_string('ucp_title', 'auth_oidc', $opname); + $PAGE->navbar->add($ucptitle, $PAGE->url); + $PAGE->set_title($ucptitle); + + echo $OUTPUT->header(); + echo \html_writer::tag('h2', $ucptitle); + echo get_string('ucp_general_intro', 'auth_oidc', $opname); + echo '

'; + + if (optional_param('o365accountconnected', null, PARAM_TEXT) == 'true') { + echo \html_writer::start_div('connectionstatus alert alert-error'); + echo \html_writer::tag('h5', get_string('ucp_o365accountconnected', 'auth_oidc')); + echo \html_writer::end_div(); + } + + // Login status. + echo \html_writer::start_div('auth_oidc_ucp_indicator'); + echo \html_writer::tag('h4', get_string('ucp_login_status', 'auth_oidc', $opname)); + if ($oidcloginconnected === true) { + echo \html_writer::tag('h4', get_string('ucp_status_enabled', 'auth_oidc'), ['class' => 'notifysuccess']); + if (is_enabled_auth('manual') === true) { + if (auth_oidc_connectioncapability($USER->id, 'disconnect')) { + $connectlinkuri = new \moodle_url('/auth/oidc/ucp.php', ['action' => 'disconnectlogin']); + $strdisconnect = get_string('ucp_login_stop', 'auth_oidc', $opname); + $linkhtml = \html_writer::link($connectlinkuri, $strdisconnect); + echo \html_writer::tag('h5', $linkhtml); + echo \html_writer::span(get_string('ucp_login_stop_desc', 'auth_oidc', $opname)); + } + } + } else { + echo \html_writer::tag('h4', get_string('ucp_status_disabled', 'auth_oidc'), ['class' => 'notifyproblem']); + if (auth_oidc_connectioncapability($USER->id, 'connect')) { + $connectlinkuri = new \moodle_url('/auth/oidc/ucp.php', ['action' => 'connectlogin']); + $linkhtml = \html_writer::link($connectlinkuri, get_string('ucp_login_start', 'auth_oidc', $opname)); + echo \html_writer::tag('h5', $linkhtml); + echo \html_writer::span(get_string('ucp_login_start_desc', 'auth_oidc', $opname)); + } + } + echo \html_writer::end_div(); + + echo $OUTPUT->footer(); +} diff --git a/auth/oidc/version.php b/auth/oidc/version.php new file mode 100644 index 00000000000..782cf2b9853 --- /dev/null +++ b/auth/oidc/version.php @@ -0,0 +1,32 @@ +. + +/** + * Plugin version information. + * + * @package auth_oidc + * @author James McQuillan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2024100710; +$plugin->requires = 2024100700; +$plugin->release = '4.5.2'; +$plugin->component = 'auth_oidc'; +$plugin->maturity = MATURITY_STABLE; From 9a878e71c36f49159d46b8e0fc4495589d275501 Mon Sep 17 00:00:00 2001 From: Binon Date: Thu, 3 Apr 2025 12:27:35 +0100 Subject: [PATCH 4/4] TD-5491, Investigate why Moodle is not redirecting to the Moodle home page when using the auth service for SSO --- auth/oidc/auth.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/auth/oidc/auth.php b/auth/oidc/auth.php index cd65c379a14..eb991a9803a 100644 --- a/auth/oidc/auth.php +++ b/auth/oidc/auth.php @@ -327,6 +327,13 @@ public function postlogout_hook($user) { preg_match("/\/oauth2\/logout$/", $logouturl)) { $logouturl .= '?post_logout_redirect_uri=' . urlencode($CFG->wwwroot); } + else{ + $tokenrec = $DB->get_record('auth_oidc_token', ['userid' => $user->id]); + $idToken = $tokenrec->idtoken; + + $logouturl .= '?post_logout_redirect_uri=' . urlencode($CFG->wwwroot); + $logouturl .= '&id_token_hint=' . $idToken; + } } redirect($logouturl);