From 1399974bac38cdab68e8963f23fd9e147ba38b2a Mon Sep 17 00:00:00 2001
From: Matt Porritt <mattp@catalyst-au.net>
Date: Fri, 19 Nov 2021 05:39:00 +0000
Subject: [PATCH] MDL-73122 Auth: Add test settings for Oauth2

Provide a test page in authentication settings
to allow administrators to test configured oAuth2
providers and to examine the data that is passed
back on a successful test user authentication.

Co-authored-by: Matt Porritt <mattp@catalyst-au.net>
---
 auth/oauth2/auth.php                          | 24 ++++++
 auth/oauth2/lang/en/auth_oauth2.php           |  6 ++
 auth/oauth2/templates/idpresponse.mustache    | 65 ++++++++++++++
 auth/oauth2/templates/idps.mustache           | 60 +++++++++++++
 auth/oauth2/test.php                          | 85 +++++++++++++++++++
 auth/oauth2/tests/behat/settings_test.feature | 26 ++++++
 6 files changed, 266 insertions(+)
 create mode 100644 auth/oauth2/templates/idpresponse.mustache
 create mode 100644 auth/oauth2/templates/idps.mustache
 create mode 100644 auth/oauth2/test.php
 create mode 100644 auth/oauth2/tests/behat/settings_test.feature

diff --git a/auth/oauth2/auth.php b/auth/oauth2/auth.php
index 0d5ecd7f79a..76700d7cd55 100644
--- a/auth/oauth2/auth.php
+++ b/auth/oauth2/auth.php
@@ -35,6 +35,30 @@ require_once($CFG->libdir.'/authlib.php');
  */
 class auth_plugin_oauth2 extends \auth_oauth2\auth {
 
+    /**
+     * Test the various configured Oauth2 providers.
+     */
+    public function test_settings() {
+        global $OUTPUT;
+
+        $authplugin = get_auth_plugin('oauth2');
+        $idps = $authplugin->loginpage_idp_list('');
+        $templateidps = [];
+
+        if (empty($idps)) {
+            echo $OUTPUT->notification(get_string('noconfiguredidps', 'auth_oauth2'), 'notifyproblem');
+            return;
+        } else {
+            foreach ($idps as $idp) {
+                $idpid = $idp['url']->get_param('id');
+                $sesskey = $idp['url']->get_param('sesskey');
+                $testurl = new moodle_url('/auth/oauth2/test.php', ['id' => $idpid, 'sesskey' => $sesskey]);
+
+                $templateidps[] = ['name' => $idp['name'], 'url' => $testurl->out(), 'iconurl' => $idp['iconurl']];
+            }
+            echo $OUTPUT->render_from_template('auth_oauth2/idps', ['idps' => $templateidps]);
+        }
+    }
 }
 
 
diff --git a/auth/oauth2/lang/en/auth_oauth2.php b/auth/oauth2/lang/en/auth_oauth2.php
index c3a4246bc9b..ce844db320f 100644
--- a/auth/oauth2/lang/en/auth_oauth2.php
+++ b/auth/oauth2/lang/en/auth_oauth2.php
@@ -88,13 +88,16 @@ $string['emailpasswordchangeinfosubject'] = '{$a}: Change password information';
 $string['info'] = 'External account';
 $string['issuer'] = 'OAuth 2 Service';
 $string['issuernologin'] = 'This issuer can not be used to login';
+$string['key'] = 'Key';
 $string['linkedlogins'] = 'Linked logins';
 $string['linkedloginshelp'] = 'Help with linked logins';
+$string['loggedin'] = 'User successfully authenticated with provider.';
 $string['loginerror_userincomplete'] = 'The user information returned did not contain a username and email address. The OAuth 2 service may be configured incorrectly.';
 $string['loginerror_nouserinfo'] = 'No user information was returned. The OAuth 2 service may be configured incorrectly.';
 $string['loginerror_invaliddomain'] = 'The email address is not allowed at this site.';
 $string['loginerror_authenticationfailed'] = 'The authentication process failed.';
 $string['loginerror_cannotcreateaccounts'] = 'An account with your email address could not be found.';
+$string['noconfiguredidps'] = 'There are no configured OAuth2 providers.';
 $string['noissuersavailable'] = 'None of the configured OAuth2 services allow you to link login accounts';
 $string['notloggedindebug'] = 'The login attempt failed. Reason: {$a}';
 $string['notwhileloggedinas'] = 'Linked logins cannot be managed while logged in as another user.';
@@ -115,3 +118,6 @@ $string['privacy:metadata:auth_oauth2:timemodified'] = 'The timestamp when this
 $string['privacy:metadata:auth_oauth2:userid'] = 'The ID of the user account which the OAuth 2 login is linked to.';
 $string['privacy:metadata:auth_oauth2:usermodified'] = 'The ID of the user who modified this account.';
 $string['privacy:metadata:auth_oauth2:username'] = 'The external username that maps to this account.';
+$string['testidplogin'] = 'Test login with:';
+$string['userinfo'] = 'User data from provider:';
+$string['value'] = 'Value';
diff --git a/auth/oauth2/templates/idpresponse.mustache b/auth/oauth2/templates/idpresponse.mustache
new file mode 100644
index 00000000000..8141ac8c47e
--- /dev/null
+++ b/auth/oauth2/templates/idpresponse.mustache
@@ -0,0 +1,65 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle 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.
+
+    Moodle 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 Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template auth_oauth2/idpresponse
+
+    Render response returned from the oAuth IdP for
+    supplied test user.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * pairs - array of key value pairs return from the oAuth IdP
+      * name - the name field from the IdP for the field
+      * value - the value of the field for the supplied test user
+
+    Example context (json):
+    {
+        "pairs": [{
+            "name": "firstname",
+            "value": "Jane"
+        },
+        {
+            "name": "lastname",
+            "value": "Awesome"
+        }]
+    }
+}}
+
+<div class="container mb-3">
+    <h3>{{#str}}userinfo, auth_oauth2{{/str}}</h3>
+    <table class="table">
+        <thead>
+        <tr>
+            <th scope="col">{{#str}}key, auth_oauth2{{/str}}</th>
+            <th scope="col">{{#str}}value, auth_oauth2{{/str}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        {{#pairs}}
+            <tr>
+                <td>{{name}}</td>
+                <td>{{{value}}}</td>
+            </tr>
+        {{/pairs}}
+        </tbody>
+    </table>
+</div>
diff --git a/auth/oauth2/templates/idps.mustache b/auth/oauth2/templates/idps.mustache
new file mode 100644
index 00000000000..0011e03d955
--- /dev/null
+++ b/auth/oauth2/templates/idps.mustache
@@ -0,0 +1,60 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle 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.
+
+    Moodle 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 Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template auth_oauth2/idps
+
+    Render available oauth2 idps for testing.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * idps - array of oAuth IdP objects
+      * name - the display name of the IdP
+      * url - the url of the oAuth test endpoint
+      * iconurl - the icon of the oAuth IdP
+
+    Example context (json):
+    {
+        "idps": [{
+            "name": "Microsoft",
+            "url": "http:\/\/localhost\/auth\/oauth2\/test.php?id=2&amp;sesskey=4js6afElZh",
+            "iconurl": "https:\/\/www.microsoft.com\/favicon.ico"
+        }]
+    }
+}}
+
+<div class="container mb-3">
+    <div class="potentialidps-header row">
+        <h3>{{#str}}testidplogin, auth_oauth2{{/str}}</h3>
+    </div>
+    <div class="potentialidps row">
+        <div class="potentialidplist">
+            {{#idps}}
+            <div class="potentialidp mb-2">
+                <a class="btn btn-secondary btn-block" href="{{{url}}}" title="{{name}}">
+                    {{#iconurl}}<img src="{{{iconurl}}}" alt="{{name}}" width="24" height="24" class="mr-1"/>{{/iconurl}}
+                    {{name}}
+                </a>
+            </div>
+            {{/idps}}
+        </div>
+    </div>
+</div>
diff --git a/auth/oauth2/test.php b/auth/oauth2/test.php
new file mode 100644
index 00000000000..7a488caac3d
--- /dev/null
+++ b/auth/oauth2/test.php
@@ -0,0 +1,85 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle 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.
+//
+// Moodle 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 Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file allows for testing of login via configured oauth2 IDP poviders.
+ *
+ * @package auth_oauth2
+ * @copyright 2021 Matt Porritt <mattp@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ */
+
+// Require_login is not needed here.
+// phpcs:disable moodle.Files.RequireLogin.Missing
+require_once('../../config.php');
+
+require_sesskey();
+
+$issuerid = required_param('id', PARAM_INT);
+$url = new moodle_url('/auth/oauth2/test.php', ['id' => $issuerid, 'sesskey' => sesskey()]);
+
+$PAGE->set_context(context_system::instance());
+$PAGE->set_url($url);
+$PAGE->set_pagelayout('admin');
+
+if (!\auth_oauth2\api::is_enabled()) {
+    throw new \moodle_exception('notenabled', 'auth_oauth2');
+}
+
+$issuer = new \core\oauth2\issuer($issuerid);
+if (!$issuer->is_available_for_login()) {
+    throw new \moodle_exception('issuernologin', 'auth_oauth2');
+}
+
+$client = \core\oauth2\api::get_user_oauth_client($issuer, $url);
+
+if ($client) {
+    // We have a valid client, now lets see if we can log into the IDP.
+    if (!$client->is_logged_in()) {
+        redirect($client->get_login_url());
+    }
+
+    echo $OUTPUT->header();
+
+    // We were successful logging into the IDP.
+    echo $OUTPUT->notification(get_string('loggedin', 'auth_oauth2'), 'notifysuccess');
+
+    // Try getting user info from the IDP.
+    $endpointurl = $client->get_issuer()->get_endpoint_url('userinfo');
+    $response = $client->get($endpointurl);
+    $userinfo = json_decode($response, true);
+
+    $templateinfo = [];
+    foreach ($userinfo as $key => $value) {
+        // We are just displaying the data from the IdP for testing purposes,
+        // so we are more interested in displaying it to the admin than
+        // processing it.
+        if (is_array($value)) {
+            $value = json_encode($value);
+        }
+        $templateinfo[] = ['name' => $key, 'value' => $value];
+    }
+
+    // Display user info.
+    if (!empty($templateinfo)) {
+        echo $OUTPUT->render_from_template('auth_oauth2/idpresponse', ['pairs' => $templateinfo]);
+    }
+
+} else {
+    throw new moodle_exception('Could not get an OAuth client.');
+}
+
+echo $OUTPUT->footer();
diff --git a/auth/oauth2/tests/behat/settings_test.feature b/auth/oauth2/tests/behat/settings_test.feature
new file mode 100644
index 00000000000..02070e59861
--- /dev/null
+++ b/auth/oauth2/tests/behat/settings_test.feature
@@ -0,0 +1,26 @@
+@auth @auth_oauth2 @javascript
+Feature: OAuth2 settings test functionality
+  In order to use them later for authentication
+  As an administrator
+  I need to be able to test configured OAuth2 login services.
+
+  Background:
+    Given I log in as "admin"
+    And I change window size to "large"
+
+  Scenario: Test oAuth2 authentication settings with no configured service.
+    Given I navigate to "Plugins > Authentication > Manage authentication" in site administration
+    And I click on "Test settings" "link" in the "OAuth 2" "table_row"
+    Then I should see "There are no configured OAuth2 providers"
+
+  Scenario: Test oAuth2 authentication settings for a configured service.
+    Given I navigate to "Server > OAuth 2 services" in site administration
+    And I press "Google"
+    And I set the following fields to these values:
+      | Name                       | Testing service                           |
+      | Client ID                  | thisistheclientid                         |
+      | Client secret              | supersecret                               |
+    And I press "Save changes"
+    And I navigate to "Plugins > Authentication > Manage authentication" in site administration
+    And I click on "Test settings" "link" in the "OAuth 2" "table_row"
+    Then I should see "Testing service"