You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2018/05/03 14:58:15 UTC

[8/8] syncope git commit: [SYNCOPE-1270] implementation for OpenID Connect for Admin Console and Enduser - This closes #74

[SYNCOPE-1270] implementation for OpenID Connect for Admin Console and Enduser - This closes #74


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/797fd1cb
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/797fd1cb
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/797fd1cb

Branch: refs/heads/master
Commit: 797fd1cbd801aedabb54b5d1dcb65d34636adec5
Parents: 064b7ef
Author: dayash <di...@tirasa.net>
Authored: Wed May 2 09:50:13 2018 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Thu May 3 16:57:56 2018 +0200

----------------------------------------------------------------------
 archetype/pom.xml                               |  31 ++
 .../archetype-resources/console/pom.xml         |   6 +
 .../resources/archetype-resources/core/pom.xml  |  11 +
 .../archetype-resources/enduser/pom.xml         |   6 +
 .../resources/META-INF/resources/app/index.html |   1 +
 .../resources/META-INF/resources/app/js/app.js  |  27 +-
 .../app/js/controllers/LoginController.js       |   2 +
 .../app/js/services/oidcProviderService.js      |  42 ++
 .../META-INF/resources/app/views/self.html      |   8 +
 deb/console/pom.xml                             |  13 +
 deb/console/src/deb/control/conffiles           |   2 +
 deb/enduser/pom.xml                             |  13 +
 deb/enduser/src/deb/control/conffiles           |   2 +
 deb/enduser/src/deb/control/preinst             |   1 +
 ext/oidcclient/agent/pom.xml                    | 126 ++++++
 .../ext/oidcclient/agent/CodeConsumer.java      |  92 +++++
 .../syncope/ext/oidcclient/agent/Constants.java |  40 ++
 .../syncope/ext/oidcclient/agent/Login.java     |  89 ++++
 .../oidcclient/agent/OIDCClientAgentSetup.java  |  77 ++++
 .../resources/oidcclient/loginError.jsp         |  35 ++
 .../resources/oidcclient/loginSuccess.jsp       |  33 ++
 .../main/resources/META-INF/web-fragment.xml    |  26 ++
 .../main/resources/oidcclient-agent.properties  |  26 ++
 ext/oidcclient/client-console/pom.xml           |  83 ++++
 .../client/console/pages/OIDCClient.java        |  68 ++++
 .../client/console/pages/OIDCClientLogin.java   |  68 ++++
 .../panels/OIDCProvidersDirectoryPanel.java     | 291 +++++++++++++
 .../console/panels/OIDCSSOLoginFormPanel.java   | 109 +++++
 .../console/rest/OIDCProviderRestClient.java    |  59 +++
 .../wizards/OIDCProviderMappingPanel.java       | 104 +++++
 .../wizards/OIDCProviderWizardBuilder.java      | 290 +++++++++++++
 .../client/console/pages/OIDCClient.html        |  37 ++
 .../client/console/pages/OIDCClient.properties  |  17 +
 .../console/pages/OIDCClient_it.properties      |  17 +
 .../console/pages/OIDCClient_pt_BR.properties   |  17 +
 .../console/pages/OIDCClient_ru.properties      |  17 +
 .../panels/OIDCProvidersDirectoryPanel.html     |  23 ++
 .../OIDCProvidersDirectoryPanel.properties      |  30 ++
 .../OIDCProvidersDirectoryPanel_it.properties   |  30 ++
 ...OIDCProvidersDirectoryPanel_pt_BR.properties |  30 ++
 .../OIDCProvidersDirectoryPanel_ru.properties   |  30 ++
 .../console/panels/OIDCSSOLoginFormPanel.html   |  25 ++
 .../OIDCProviderWizardBuilder$Mapping.html      |  23 ++
 .../wizards/OIDCProviderWizardBuilder$OP.html   |  28 ++
 .../OIDCProviderWizardBuilder$OP.properties     |  24 ++
 .../OIDCProviderWizardBuilder$OPContinue.html   |  33 ++
 ...CProviderWizardBuilder$OPContinue.properties |  27 ++
 ...oviderWizardBuilder$OPContinue_it.properties |  27 ++
 ...derWizardBuilder$OPContinue_pt_BR.properties |  27 ++
 ...oviderWizardBuilder$OPContinue_ru.properties |  27 ++
 .../OIDCProviderWizardBuilder$OP_it.properties  |  24 ++
 ...IDCProviderWizardBuilder$OP_pt_BR.properties |  24 ++
 .../OIDCProviderWizardBuilder$OP_ru.properties  |  24 ++
 ext/oidcclient/client-enduser/pom.xml           |  83 ++++
 .../client/enduser/pages/OIDCClientLogin.java   |  63 +++
 .../resources/OIDCProvidersResource.java        |  69 ++++
 ext/oidcclient/common-lib/pom.xml               |  56 +++
 .../syncope/common/lib/OIDCConstants.java       |  45 ++
 .../common/lib/to/OIDCLoginRequestTO.java       |  91 +++++
 .../common/lib/to/OIDCLoginResponseTO.java      | 133 ++++++
 .../syncope/common/lib/to/OIDCProviderTO.java   | 219 ++++++++++
 .../common/lib/types/OIDCClientEntitlement.java |  57 +++
 ext/oidcclient/logic/pom.xml                    |  76 ++++
 .../syncope/core/logic/OIDCClientLogic.java     | 406 +++++++++++++++++++
 .../syncope/core/logic/OIDCProviderLogic.java   | 167 ++++++++
 ...ClientClassPathScanImplementationLookup.java |  78 ++++
 .../core/logic/init/OIDCClientLoader.java       |  55 +++
 .../model/OIDCProviderDiscoveryDocument.java    | 128 ++++++
 .../core/logic/model/TokenEndpointResponse.java |  86 ++++
 .../core/logic/oidc/OIDCUserManager.java        | 292 +++++++++++++
 ext/oidcclient/persistence-api/pom.xml          |  61 +++
 .../persistence/api/dao/OIDCProviderDAO.java    |  35 ++
 .../api/entity/OIDCEntityFactory.java           |  25 ++
 .../persistence/api/entity/OIDCProvider.java    |  85 ++++
 .../api/entity/OIDCProviderItem.java            |  29 ++
 .../api/entity/OIDCUserTemplate.java            |  27 ++
 ext/oidcclient/persistence-jpa/pom.xml          | 134 ++++++
 .../persistence/jpa/dao/JPAOIDCProviderDAO.java |  78 ++++
 .../jpa/entity/JPAOIDCEntityFactory.java        |  49 +++
 .../persistence/jpa/entity/JPAOIDCProvider.java | 250 ++++++++++++
 .../jpa/entity/JPAOIDCProviderItem.java         |  79 ++++
 .../jpa/entity/JPAOIDCUserTemplate.java         |  52 +++
 ext/oidcclient/pom.xml                          |  55 +++
 ext/oidcclient/provisioning-api/pom.xml         |  67 +++
 .../provisioning/api/OIDCProviderActions.java   |  35 ++
 .../api/data/OIDCProviderDataBinder.java        |  32 ++
 ext/oidcclient/provisioning-java/pom.xml        |  61 +++
 .../java/DefaultOIDCProviderActions.java        |  48 +++
 .../java/data/OIDCProviderDataBinderImpl.java   | 258 ++++++++++++
 ext/oidcclient/rest-api/pom.xml                 |  77 ++++
 .../rest/api/service/OIDCClientService.java     |  75 ++++
 .../rest/api/service/OIDCProviderService.java   | 156 +++++++
 ext/oidcclient/rest-cxf/pom.xml                 |  72 ++++
 .../rest/cxf/service/OIDCClientServiceImpl.java |  44 ++
 .../cxf/service/OIDCProviderServiceImpl.java    |  83 ++++
 ext/pom.xml                                     |   1 +
 .../syncope/ext/saml2lsp/agent/Login.java       |   2 +-
 fit/console-reference/pom.xml                   |   6 +
 .../main/resources/oidcclient-agent.properties  |  26 ++
 .../src/main/webapp/WEB-INF/web.xml             |  10 +
 .../src/test/resources/rebel.xml                |   8 +
 fit/core-reference/pom.xml                      |  23 ++
 .../org/apache/syncope/fit/AbstractITCase.java  |   8 +
 .../apache/syncope/fit/OIDCClientDetector.java  |  52 +++
 .../syncope/fit/core/OIDCClientITCase.java      | 152 +++++++
 fit/core-reference/src/test/resources/rebel.xml |  17 +
 fit/enduser-reference/pom.xml                   |   6 +
 .../main/resources/oidcclient-agent.properties  |  26 ++
 .../src/main/webapp/WEB-INF/web.xml             |  10 +
 .../src/test/resources/rebel.xml                |   8 +
 pom.xml                                         |  10 +
 .../reference-guide/concepts/extensions.adoc    |  33 ++
 .../workingwithapachesyncope/customization.adoc |  52 +++
 113 files changed, 6861 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/archetype/pom.xml
----------------------------------------------------------------------
diff --git a/archetype/pom.xml b/archetype/pom.xml
index c4639be..27eccc4 100644
--- a/archetype/pom.xml
+++ b/archetype/pom.xml
@@ -233,6 +233,13 @@ under the License.
         </includes>
       </resource>
       <resource>
+        <directory>../ext/oidcclient/agent/src/main/resources</directory>
+        <targetPath>${project.build.outputDirectory}/archetype-resources/console/src/main/resources/all</targetPath>
+        <includes>
+          <include>oidcclient-agent.properties</include>
+        </includes>
+      </resource>
+      <resource>
         <directory>../fit/console-reference/src/main/resources</directory>
         <targetPath>${project.build.outputDirectory}/archetype-resources/console/src/main/resources</targetPath>
         <includes>
@@ -252,6 +259,14 @@ under the License.
         </includes>
       </resource>
       <resource>
+        <directory>../fit/console-reference/src/main/resources</directory>
+        <targetPath>${project.build.outputDirectory}/archetype-resources/console/src/test/resources</targetPath>
+        <includes>
+          <include>console.properties</include>
+          <include>oidcclient-agent.properties</include>
+        </includes>
+      </resource>
+      <resource>
         <directory>../fit/console-reference/src/main/webapp/WEB-INF</directory>
         <targetPath>${project.build.outputDirectory}/archetype-resources/console/src/main/webapp/WEB-INF</targetPath>
       </resource>
@@ -272,6 +287,13 @@ under the License.
         </includes>
       </resource>
       <resource>
+        <directory>../ext/oidcclient/agent/src/main/resources</directory>
+        <targetPath>${project.build.outputDirectory}/archetype-resources/enduser/src/main/resources/all</targetPath>
+        <includes>
+          <include>oidcclient-agent.properties</include>
+        </includes>
+      </resource>
+      <resource>
         <directory>../client/enduser/src/main/resources/META-INF/resources/app</directory>
         <targetPath>${project.build.outputDirectory}/archetype-resources/enduser/src/main/webapp/app</targetPath>
       </resource>
@@ -292,6 +314,15 @@ under the License.
         </includes>
       </resource>
       <resource>
+        <directory>../fit/enduser-reference/src/main/resources</directory>
+        <targetPath>${project.build.outputDirectory}/archetype-resources/enduser/src/test/resources</targetPath>
+        <includes>
+          <include>enduser.properties</include>
+          <include>oidcclient-agent.properties</include>
+          <include>customForm.json</include>
+        </includes>
+      </resource>
+      <resource>
         <directory>../fit/enduser-reference/src/main/webapp/WEB-INF</directory>
         <targetPath>${project.build.outputDirectory}/archetype-resources/enduser/src/main/webapp/WEB-INF</targetPath>
       </resource>

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/archetype/src/main/resources/archetype-resources/console/pom.xml
----------------------------------------------------------------------
diff --git a/archetype/src/main/resources/archetype-resources/console/pom.xml b/archetype/src/main/resources/archetype-resources/console/pom.xml
index 401387e..479126c 100644
--- a/archetype/src/main/resources/archetype-resources/console/pom.xml
+++ b/archetype/src/main/resources/archetype-resources/console/pom.xml
@@ -141,6 +141,12 @@ under the License.
         </dependency>
         
         <dependency>
+          <groupId>org.apache.syncope.ext.oidcclient</groupId>
+          <artifactId>syncope-ext-oidcclient-client-console</artifactId>
+          <version>${syncope.version}</version>
+        </dependency>
+        
+        <dependency>
           <groupId>org.apache.syncope.ext.scimv2</groupId>
           <artifactId>syncope-ext-scimv2-client-console</artifactId>
           <version>${syncope.version}</version>

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/archetype/src/main/resources/archetype-resources/core/pom.xml
----------------------------------------------------------------------
diff --git a/archetype/src/main/resources/archetype-resources/core/pom.xml b/archetype/src/main/resources/archetype-resources/core/pom.xml
index 68d62b3..f248d9a 100644
--- a/archetype/src/main/resources/archetype-resources/core/pom.xml
+++ b/archetype/src/main/resources/archetype-resources/core/pom.xml
@@ -193,6 +193,17 @@ under the License.
         </dependency>
 
         <dependency>
+          <groupId>org.apache.syncope.ext.oidcclient</groupId>
+          <artifactId>syncope-ext-oidcclient-rest-cxf</artifactId>
+          <version>${syncope.version}</version>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.syncope.ext.oidcclient</groupId>
+          <artifactId>syncope-ext-oidcclient-persistence-jpa</artifactId>
+          <version>${syncope.version}</version>
+        </dependency>
+
+        <dependency>
           <groupId>org.apache.syncope.ext.scimv2</groupId>
           <artifactId>syncope-ext-scimv2-rest-cxf</artifactId>
           <version>${syncope.version}</version>

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/archetype/src/main/resources/archetype-resources/enduser/pom.xml
----------------------------------------------------------------------
diff --git a/archetype/src/main/resources/archetype-resources/enduser/pom.xml b/archetype/src/main/resources/archetype-resources/enduser/pom.xml
index 0b097f3..5f98d31 100644
--- a/archetype/src/main/resources/archetype-resources/enduser/pom.xml
+++ b/archetype/src/main/resources/archetype-resources/enduser/pom.xml
@@ -332,6 +332,12 @@ under the License.
           <artifactId>syncope-ext-saml2sp-client-enduser</artifactId>
           <version>${syncope.version}</version>
         </dependency>
+
+        <dependency>
+          <groupId>org.apache.syncope.ext.oidcclient</groupId>
+          <artifactId>syncope-ext-oidcclient-client-enduser</artifactId>
+          <version>${syncope.version}</version>
+        </dependency>
       </dependencies>
       
       <build>

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/client/enduser/src/main/resources/META-INF/resources/app/index.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/index.html b/client/enduser/src/main/resources/META-INF/resources/app/index.html
index cd4542a..2613a6e 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/index.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/index.html
@@ -100,6 +100,7 @@ under the License.
   <script src="js/services/groupService.js"></script>
   <script src="js/services/anyService.js"></script>
   <script src="js/services/saml2IdPService.js"></script>
+  <script src="js/services/oidcProviderService.js"></script>
   <script src="js/services/saml2SPService.js"></script>
   <!--controllers-->
   <script src="js/controllers/HomeController.js"></script>

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
index 3ae4f9d..1019cad 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
@@ -341,7 +341,8 @@ app.run(['$rootScope', '$location', '$state', 'AuthService',
     };
   }]);
 app.controller('ApplicationController', ['$scope', '$rootScope', '$location', 'InfoService', 'SAML2IdPService',
-  function ($scope, $rootScope, $location, InfoService, SAML2IdPService) {
+  'OIDCProviderService',
+  function ($scope, $rootScope, $location, InfoService, SAML2IdPService, OIDCProviderService) {
     $scope.initApplication = function () {
       /* 
        * disable by default wizard buttons in self-registration
@@ -377,6 +378,10 @@ app.controller('ApplicationController', ['$scope', '$rootScope', '$location', 'I
         available: [],
         selected: {}
       };
+      $rootScope.oidcops = {
+        available: [],
+        selected: {}
+      };
 
       InfoService.getInfo().then(
               function (response) {
@@ -406,6 +411,19 @@ app.controller('ApplicationController', ['$scope', '$rootScope', '$location', 'I
       /* 
        * configuration getters
        */
+
+      /* <Extensions> */
+      OIDCProviderService.getAvailableOIDCProviders().then(
+              function (response) {
+                $rootScope.oidcops.available = response;
+              },
+              function (response) {
+                console.debug("No OIDC Client extension available", response);
+              });
+      /* </Extensions> */
+      /* 
+       * configuration getters
+       */
       $rootScope.isSelfRegAllowed = function () {
         return $rootScope.selfRegAllowed === true;
       };
@@ -418,6 +436,13 @@ app.controller('ApplicationController', ['$scope', '$rootScope', '$location', 'I
       $rootScope.saml2login = function () {
         window.location.href = '../saml2sp/login?idp=' + $rootScope.saml2idps.selected.entityID;
       };
+
+      $rootScope.oidcclientExtAvailable = function () {
+        return $rootScope.oidcops.available.length > 0;
+      };
+      $rootScope.oidclogin = function () {
+        window.location.href = '../oidcclient/login?op=' + $rootScope.oidcops.selected.name;
+      };
       $rootScope.getVersion = function () {
         return $rootScope.version;
       };

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js
index b58aced..d2b755c 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js
@@ -34,6 +34,8 @@ angular.module("login").controller("LoginController", ['$scope', '$rootScope', '
         $scope.credentials.errorMessage = '';
         // reset SAML 2.0 entityID
         $rootScope.saml2idps.selected.entityID = null;
+        // reset OIDC name
+        $rootScope.oidcops.selected.name = null;
         // got to update page
         $location.path("/self/update");
       }, function (response) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/client/enduser/src/main/resources/META-INF/resources/app/js/services/oidcProviderService.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/oidcProviderService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/oidcProviderService.js
new file mode 100644
index 0000000..d2ce926
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/oidcProviderService.js
@@ -0,0 +1,42 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+'use strict';
+
+angular.module('self')
+        .factory('OIDCProviderService', ['$resource', '$q', '$http',
+          function ($resource, $q, $http) {
+
+            var oidcProviderService = {};
+
+            oidcProviderService.getAvailableOIDCProviders = function () {
+              return $http.get("../api/oidcProviders")
+                      .then(function (response) {
+                        return response.data;
+                      }, function (response) {
+                        console.error("Something went wrong during oidcProviders extension retrieval, exit with status: ",
+                                response);
+                        return $q.reject(response.data || response.statusText);
+                      });
+            };
+
+            return oidcProviderService;
+          }]);
+
+

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/client/enduser/src/main/resources/META-INF/resources/app/views/self.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/self.html b/client/enduser/src/main/resources/META-INF/resources/app/views/self.html
index 1cc279e..cedb21c 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/self.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/self.html
@@ -62,6 +62,14 @@ under the License.
                           ng-model="saml2idps.selected" ng-change="saml2login()">
                   </select>
                 </div>
+                <div class="form-group">
+                  <span ng-if="$root.oidcclientExtAvailable()">OpenID Connect</span>
+                  <select id="oiscclient" style="width: 100%; text-align: left;" class="btn dropdown-toggle btn-default"
+                          ng-if="$root.oidcclientExtAvailable()"
+                          ng-options="op.name for op in oidcops.available track by op.name"
+                          ng-model="oidcops.selected" ng-change="oidclogin()">
+                  </select>
+                </div>
               </fieldset>
             </form>
           </div>

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/deb/console/pom.xml
----------------------------------------------------------------------
diff --git a/deb/console/pom.xml b/deb/console/pom.xml
index 3a4df52..85c6495 100644
--- a/deb/console/pom.xml
+++ b/deb/console/pom.xml
@@ -55,6 +55,11 @@ under the License.
       <version>${project.version}</version>
     </dependency>
     <dependency>
+      <groupId>org.apache.syncope.ext.oidcclient</groupId>
+      <artifactId>syncope-ext-oidcclient-client-console</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
       <groupId>org.apache.syncope.ext.scimv2</groupId>
       <artifactId>syncope-ext-scimv2-client-console</artifactId>
       <version>${project.version}</version>
@@ -116,6 +121,14 @@ under the License.
         <filtering>true</filtering>
       </resource>
       <resource>
+        <directory>${project.basedir}/../../ext/oidcclient/agent/src/main/resources</directory>
+        <includes>
+          <include>oidcclient-agent.properties</include>
+        </includes>
+        <targetPath>${project.build.directory}/etc</targetPath>
+        <filtering>true</filtering>
+      </resource>
+      <resource>
         <directory>${basedir}/../../fit/console-reference/src/main/resources</directory>
         <includes>
           <include>log4j2.xml</include>

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/deb/console/src/deb/control/conffiles
----------------------------------------------------------------------
diff --git a/deb/console/src/deb/control/conffiles b/deb/console/src/deb/control/conffiles
index 47e5201..a9bfb08 100644
--- a/deb/console/src/deb/control/conffiles
+++ b/deb/console/src/deb/control/conffiles
@@ -1,3 +1,5 @@
 /etc/tomcat8/Catalina/localhost/syncope-console.xml
 /etc/apache-syncope/console.properties
 /etc/apache-syncope/saml2sp-agent.properties
+/etc/apache-syncope/oidcclient-agent.properties
+

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/deb/enduser/pom.xml
----------------------------------------------------------------------
diff --git a/deb/enduser/pom.xml b/deb/enduser/pom.xml
index 322b478..c27bf75 100644
--- a/deb/enduser/pom.xml
+++ b/deb/enduser/pom.xml
@@ -44,6 +44,11 @@ under the License.
       <version>${project.version}</version>
     </dependency>
     <dependency>
+      <groupId>org.apache.syncope.ext.oidcclient</groupId>
+      <artifactId>syncope-ext-oidcclient-client-enduser</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
       <groupId>org.apache.syncope.client</groupId>
       <artifactId>syncope-client-enduser</artifactId>
       <version>${project.version}</version>
@@ -106,6 +111,14 @@ under the License.
         <filtering>true</filtering>
       </resource>
       <resource>
+        <directory>${project.basedir}/../../ext/oidcclient/agent/src/main/resources</directory>
+        <includes>
+          <include>oidcclient-agent.properties</include>
+        </includes>
+        <targetPath>${project.build.directory}/etc</targetPath>
+        <filtering>true</filtering>
+      </resource>
+      <resource>
         <directory>${basedir}/../../fit/enduser-reference/src/main/resources</directory>
         <includes>
           <include>log4j2.xml</include>

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/deb/enduser/src/deb/control/conffiles
----------------------------------------------------------------------
diff --git a/deb/enduser/src/deb/control/conffiles b/deb/enduser/src/deb/control/conffiles
index 934c7fb..8fb2453 100644
--- a/deb/enduser/src/deb/control/conffiles
+++ b/deb/enduser/src/deb/control/conffiles
@@ -2,3 +2,5 @@
 /etc/apache-syncope/enduser.properties
 /etc/apache-syncope/customForm.json
 /etc/apache-syncope/saml2sp-agent.properties
+/etc/apache-syncope/oidcclient-agent.properties
+

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/deb/enduser/src/deb/control/preinst
----------------------------------------------------------------------
diff --git a/deb/enduser/src/deb/control/preinst b/deb/enduser/src/deb/control/preinst
index afdca38..0e88785 100644
--- a/deb/enduser/src/deb/control/preinst
+++ b/deb/enduser/src/deb/control/preinst
@@ -16,3 +16,4 @@
 # specific language governing permissions and limitations
 # under the License.
 dpkg-divert --add --package apache-syncope-enduser --rename --divert /etc/apache-syncope/saml2sp-agent.properties.disabled /etc/apache-syncope/saml2sp-agent.properties
+dpkg-divert --add --package apache-syncope-enduser --rename --divert /etc/apache-syncope/oidcclient-agent.properties.disabled /etc/apache-syncope/oidcclient-agent.properties

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/agent/pom.xml
----------------------------------------------------------------------
diff --git a/ext/oidcclient/agent/pom.xml b/ext/oidcclient/agent/pom.xml
new file mode 100644
index 0000000..b4c218e
--- /dev/null
+++ b/ext/oidcclient/agent/pom.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.syncope.ext</groupId>
+    <artifactId>syncope-ext-oidcclient</artifactId>
+    <version>2.1.0-SNAPSHOT</version>
+  </parent>
+
+  <name>Apache Syncope Ext: OIDC Client Agent</name>
+  <description>Apache Syncope Ext: OIDC Client Agent</description>
+  <groupId>org.apache.syncope.ext.oidcclient</groupId>
+  <artifactId>syncope-ext-oidcclient-agent</artifactId>
+  <packaging>jar</packaging>
+  
+  <properties>
+    <rootpom.basedir>${basedir}/../../..</rootpom.basedir>
+  </properties>
+
+  <dependencies>
+    <dependency> 
+      <groupId>javax.servlet</groupId> 
+      <artifactId>javax.servlet-api</artifactId> 
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet.jsp</groupId>
+      <artifactId>javax.servlet.jsp-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>jstl</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.syncope.client</groupId>
+      <artifactId>syncope-client-lib</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.ext.oidcclient</groupId>
+      <artifactId>syncope-ext-oidcclient-rest-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+  
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
+    </plugins>
+    
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+      </resource>
+      
+      <resource>
+        <directory>${basedir}</directory>
+        <targetPath>META-INF</targetPath>
+        <includes>
+          <include>LICENSE</include>
+          <include>NOTICE</include>
+        </includes>
+      </resource>
+    </resources>
+  </build>
+  
+  <profiles>
+    <profile>
+      <id>apache-release</id>
+
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-source-plugin</artifactId>
+            <inherited>false</inherited>
+            <executions>
+              <execution>
+                <id>attach-sources</id>
+                <goals>
+                  <goal>jar-no-fork</goal>
+                </goals>
+                <configuration>
+                  <includes>
+                    <include>${basedir}/LICENSE</include>
+                    <include>${basedir}/NOTICE</include>
+                  </includes>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/CodeConsumer.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/CodeConsumer.java b/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/CodeConsumer.java
new file mode 100644
index 0000000..08e9d23
--- /dev/null
+++ b/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/CodeConsumer.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.ext.oidcclient.agent;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.OIDCConstants;
+import org.apache.syncope.common.lib.to.OIDCLoginResponseTO;
+import org.apache.syncope.common.rest.api.service.OIDCClientService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@WebServlet(name = "codeConsumer", urlPatterns = { "/oidcclient/code-consumer" })
+public class CodeConsumer extends HttpServlet {
+
+    private static final long serialVersionUID = 968480296813639041L;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(CodeConsumer.class);
+
+    @Override
+    protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
+            throws ServletException, IOException {
+
+        try {
+            String authorizationCode = request.getParameter(OIDCConstants.CODE);
+            String state = request.getParameter(OIDCConstants.STATE);
+            if (StringUtils.isBlank(authorizationCode) || StringUtils.isBlank(state)) {
+                throw new IllegalArgumentException("Empty " + OIDCConstants.CODE + " or " + OIDCConstants.STATE);
+            }
+            if (state.equals(request.getSession().getAttribute(OIDCConstants.STATE).toString())) {
+                SyncopeClient anonymous = (SyncopeClient) request.getServletContext().
+                        getAttribute(Constants.SYNCOPE_ANONYMOUS_CLIENT);
+
+                OIDCLoginResponseTO responseTO = anonymous.getService(OIDCClientService.class).login(
+                        request.getRequestURL().toString(),
+                        authorizationCode,
+                        request.getSession().getAttribute(OIDCConstants.OP).toString());
+                request.getSession().setAttribute(
+                        Constants.OIDCCLIENTJWT, responseTO.getAccessToken());
+                request.getSession().setAttribute(
+                        Constants.OIDCCLIENTJWT_EXPIRE, responseTO.getAccessTokenExpiryTime());
+
+                String successURL = getServletContext().getInitParameter(Constants.CONTEXT_PARAM_LOGIN_SUCCESS_URL);
+                if (successURL == null) {
+                    request.setAttribute("responseTO", responseTO);
+                    request.getRequestDispatcher("loginSuccess.jsp").forward(request, response);
+                } else {
+                    response.sendRedirect(successURL);
+                }
+            } else {
+                throw new IllegalArgumentException("Invalid " + OIDCConstants.STATE + " provided");
+            }
+        } catch (Exception e) {
+            LOG.error("While processing authentication response from OP", e);
+            String errorURL = getServletContext().getInitParameter(Constants.CONTEXT_PARAM_LOGIN_ERROR_URL);
+
+            if (errorURL == null) {
+                request.setAttribute("exception", e);
+                request.getRequestDispatcher("loginError.jsp").forward(request, response);
+
+                e.printStackTrace(response.getWriter());
+            } else {
+                response.sendRedirect(errorURL + "?errorMessage="
+                        + URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.name()));
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/Constants.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/Constants.java b/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/Constants.java
new file mode 100644
index 0000000..1ff3327
--- /dev/null
+++ b/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/Constants.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.ext.oidcclient.agent;
+
+public final class Constants {
+
+    public static final String SYNCOPE_ANONYMOUS_CLIENT = "SyncopeAnonymousClient";
+
+    public static final String SYNCOPE_CLIENT_FACTORY = "SyncopeClientFactory";
+
+    public static final String PARAM_OP = "op";
+
+    public static final String CONTEXT_PARAM_LOGIN_SUCCESS_URL = "oidcclient.login.success.url";
+
+    public static final String CONTEXT_PARAM_LOGIN_ERROR_URL = "oidcclient.login.error.url";
+
+    public static final String OIDCCLIENTJWT = "oidcclient.jwt";
+
+    public static final String OIDCCLIENTJWT_EXPIRE = "oidcclient.jwt.expire";
+
+    private Constants() {
+        // private constructor for static utility class
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/Login.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/Login.java b/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/Login.java
new file mode 100644
index 0000000..36850e4
--- /dev/null
+++ b/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/Login.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.ext.oidcclient.agent;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.UriBuilder;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.OIDCConstants;
+import org.apache.syncope.common.lib.to.OIDCLoginRequestTO;
+import org.apache.syncope.common.rest.api.service.OIDCClientService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@WebServlet(name = "oidcclientLogin", urlPatterns = { "/oidcclient/login" })
+public class Login extends HttpServlet {
+
+    private static final long serialVersionUID = 968480296813639041L;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(Login.class);
+
+    @Override
+    protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
+            throws ServletException, IOException {
+
+        String op = request.getParameter(Constants.PARAM_OP);
+
+        SyncopeClient anonymous = (SyncopeClient) request.getServletContext().
+                getAttribute(Constants.SYNCOPE_ANONYMOUS_CLIENT);
+        try {
+            String redirectURI = StringUtils.substringBefore(request.getRequestURL().toString(), "/login")
+                    + "/code-consumer";
+            OIDCLoginRequestTO requestTO = anonymous.getService(OIDCClientService.class).
+                    createLoginRequest(redirectURI, op);
+
+            request.getSession().setAttribute(OIDCConstants.STATE, requestTO.getState());
+            request.getSession().setAttribute(OIDCConstants.OP, op);
+
+            response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store");
+            response.setHeader("Pragma", "no-cache");
+            response.setStatus(HttpServletResponse.SC_SEE_OTHER);
+
+            UriBuilder ub = UriBuilder.fromUri(requestTO.getProviderAddress());
+            ub.queryParam(OIDCConstants.CLIENT_ID, requestTO.getClientId());
+            ub.queryParam(OIDCConstants.REDIRECT_URI, requestTO.getRedirectURI());
+            ub.queryParam(OIDCConstants.RESPOSNSE_TYPE, requestTO.getResponseType());
+            ub.queryParam(OIDCConstants.SCOPE, requestTO.getScope());
+            ub.queryParam(OIDCConstants.STATE, requestTO.getState());
+            response.setHeader("Location", ub.build().toASCIIString());
+        } catch (Exception e) {
+            LOG.error("While preparing the Authentication Request", e);
+
+            String errorURL = getServletContext().getInitParameter(Constants.CONTEXT_PARAM_LOGIN_ERROR_URL);
+            if (errorURL == null) {
+                request.setAttribute("exception", e);
+                request.getRequestDispatcher("loginError.jsp").forward(request, response);
+
+                e.printStackTrace(response.getWriter());
+            } else {
+                response.sendRedirect(errorURL + "?errorMessage="
+                        + URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.name()));
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/OIDCClientAgentSetup.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/OIDCClientAgentSetup.java b/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/OIDCClientAgentSetup.java
new file mode 100644
index 0000000..887d0d6
--- /dev/null
+++ b/ext/oidcclient/agent/src/main/java/org/apache/syncope/ext/oidcclient/agent/OIDCClientAgentSetup.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.ext.oidcclient.agent;
+
+import java.util.Properties;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.annotation.WebListener;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
+import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
+import org.apache.syncope.common.lib.PropertyUtils;
+
+@WebListener
+public class OIDCClientAgentSetup implements ServletContextListener {
+
+    private static final String OIDCCLIENT_AGENT_PROPERTIES = "oidcclient-agent.properties";
+
+    private static <T> T assertNotNull(final T argument, final String name) {
+        if (argument == null) {
+            throw new IllegalArgumentException("Argument '" + name + "' may not be null.");
+        }
+        return argument;
+    }
+
+    @Override
+    public void contextInitialized(final ServletContextEvent sce) {
+        // read oidcclientagent.properties
+        Properties props = PropertyUtils.read(getClass(), OIDCCLIENT_AGENT_PROPERTIES, "conf.directory").getLeft();
+
+        String anonymousUser = props.getProperty("anonymousUser");
+        assertNotNull(anonymousUser, "<anonymousUser>");
+        String anonymousKey = props.getProperty("anonymousKey");
+        assertNotNull(anonymousKey, "<anonymousKey>");
+
+        String scheme = props.getProperty("scheme");
+        assertNotNull(scheme, "<scheme>");
+        String host = props.getProperty("host");
+        assertNotNull(host, "<host>");
+        String port = props.getProperty("port");
+        assertNotNull(port, "<port>");
+        String rootPath = props.getProperty("rootPath");
+        assertNotNull(rootPath, "<rootPath>");
+        String useGZIPCompression = props.getProperty("useGZIPCompression");
+        assertNotNull(useGZIPCompression, "<useGZIPCompression>");
+
+        SyncopeClientFactoryBean clientFactory = new SyncopeClientFactoryBean().
+                setAddress(scheme + "://" + host + ":" + port + "/" + rootPath).
+                setUseCompression(BooleanUtils.toBoolean(useGZIPCompression));
+
+        sce.getServletContext().setAttribute(Constants.SYNCOPE_CLIENT_FACTORY, clientFactory);
+        sce.getServletContext().setAttribute(
+                Constants.SYNCOPE_ANONYMOUS_CLIENT,
+                clientFactory.create(new AnonymousAuthenticationHandler(anonymousUser, anonymousKey)));
+    }
+
+    @Override
+    public void contextDestroyed(final ServletContextEvent sce) {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/agent/src/main/resources/META-INF/resources/oidcclient/loginError.jsp
----------------------------------------------------------------------
diff --git a/ext/oidcclient/agent/src/main/resources/META-INF/resources/oidcclient/loginError.jsp b/ext/oidcclient/agent/src/main/resources/META-INF/resources/oidcclient/loginError.jsp
new file mode 100644
index 0000000..b3c80d9
--- /dev/null
+++ b/ext/oidcclient/agent/src/main/resources/META-INF/resources/oidcclient/loginError.jsp
@@ -0,0 +1,35 @@
+<%--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+--%>
+<%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
+<%
+    Exception exception = (Exception) request.getAttribute("exception");
+%>
+<html>
+  <head>
+    <title>Apache Syncope ${syncope.version} - OIDC CLIENT - Login Error</title>
+  </head>
+  <body>
+    <h1>An error was found</h1>
+
+    <h2><%=exception.getMessage()%></h2>
+    <pre>
+      <%exception.printStackTrace(new java.io.PrintWriter(out));%>
+    </pre>
+  </body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/agent/src/main/resources/META-INF/resources/oidcclient/loginSuccess.jsp
----------------------------------------------------------------------
diff --git a/ext/oidcclient/agent/src/main/resources/META-INF/resources/oidcclient/loginSuccess.jsp b/ext/oidcclient/agent/src/main/resources/META-INF/resources/oidcclient/loginSuccess.jsp
new file mode 100644
index 0000000..7361842
--- /dev/null
+++ b/ext/oidcclient/agent/src/main/resources/META-INF/resources/oidcclient/loginSuccess.jsp
@@ -0,0 +1,33 @@
+<%--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+--%>
+<%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
+<%@page import="org.apache.syncope.common.lib.to.OIDCLoginResponseTO"%>
+<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
+<c:set var="responseTO" value="${requestScope['responseTO']}"/>
+<html>
+  <head>
+    <title>Apache Syncope ${syncope.version} - OIDC CLIENT - Successful Login</title>
+  </head>
+  <body>
+    <h1>Welcome ${responseTO.userName}</h1>
+
+    <p>You have been successfully authenticated by the requested OIDC Provider</p>
+
+  </body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/agent/src/main/resources/META-INF/web-fragment.xml
----------------------------------------------------------------------
diff --git a/ext/oidcclient/agent/src/main/resources/META-INF/web-fragment.xml b/ext/oidcclient/agent/src/main/resources/META-INF/web-fragment.xml
new file mode 100644
index 0000000..fab858a
--- /dev/null
+++ b/ext/oidcclient/agent/src/main/resources/META-INF/web-fragment.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<web-fragment xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+              xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
+                                  http://xmlns.jcp.org/xml/ns/javaee/web-fragment_3_1.xsd"
+              id="${pom.artifactId}" version="3.1">
+
+</web-fragment>

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/agent/src/main/resources/oidcclient-agent.properties
----------------------------------------------------------------------
diff --git a/ext/oidcclient/agent/src/main/resources/oidcclient-agent.properties b/ext/oidcclient/agent/src/main/resources/oidcclient-agent.properties
new file mode 100644
index 0000000..d3fee30
--- /dev/null
+++ b/ext/oidcclient/agent/src/main/resources/oidcclient-agent.properties
@@ -0,0 +1,26 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+conf.directory=${conf.directory}
+
+anonymousUser=${anonymousUser}
+anonymousKey=${anonymousKey}
+
+scheme=http
+host=localhost
+port=8080
+rootPath=/syncope/rest/
+useGZIPCompression=true

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/client-console/pom.xml
----------------------------------------------------------------------
diff --git a/ext/oidcclient/client-console/pom.xml b/ext/oidcclient/client-console/pom.xml
new file mode 100644
index 0000000..59ba01a
--- /dev/null
+++ b/ext/oidcclient/client-console/pom.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.syncope.ext</groupId>
+    <artifactId>syncope-ext-oidcclient</artifactId>
+    <version>2.1.0-SNAPSHOT</version>
+  </parent>
+
+  <name>Apache Syncope Ext: OIDC Client Console</name>
+  <description>Apache Syncope Ext: OIDC Client Console</description>
+  <groupId>org.apache.syncope.ext.oidcclient</groupId>
+  <artifactId>syncope-ext-oidcclient-client-console</artifactId>
+  <packaging>jar</packaging>
+  
+  <properties>
+    <rootpom.basedir>${basedir}/../../..</rootpom.basedir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.syncope.ext.oidcclient</groupId>
+      <artifactId>syncope-ext-oidcclient-common-lib</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.ext.oidcclient</groupId>
+      <artifactId>syncope-ext-oidcclient-rest-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.ext.oidcclient</groupId>
+      <artifactId>syncope-ext-oidcclient-agent</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.client</groupId>
+      <artifactId>syncope-client-console</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
+    </plugins>
+    
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+      </resource>
+    </resources>
+  </build>
+</project>

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClient.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClient.java b/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClient.java
new file mode 100644
index 0000000..f588733
--- /dev/null
+++ b/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClient.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.console.pages;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.tabs.AjaxBootstrapTabbedPanel;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
+import org.apache.syncope.client.console.annotations.ExtPage;
+import org.apache.syncope.client.console.panels.OIDCProvidersDirectoryPanel;
+import org.apache.syncope.common.lib.types.OIDCClientEntitlement;
+import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
+import org.apache.wicket.extensions.markup.html.tabs.ITab;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+@ExtPage(label = "OIDC Client", icon = "fa-openid ", listEntitlement = OIDCClientEntitlement.OP_READ, priority = 100)
+public class OIDCClient extends BaseExtPage {
+
+    private static final long serialVersionUID = -599601954212606001L;
+
+    public OIDCClient(final PageParameters parameters) {
+        super(parameters);
+
+        body.add(BookmarkablePageLinkBuilder.build("dashboard", "dashboardBr", Dashboard.class));
+
+        WebMarkupContainer content = new WebMarkupContainer("content");
+        content.setOutputMarkupId(true);
+        content.add(new AjaxBootstrapTabbedPanel<>("tabbedPanel", buildTabList()));
+        body.add(content);
+    }
+
+    private List<ITab> buildTabList() {
+
+        final List<ITab> tabs = new ArrayList<>(1);
+
+        tabs.add(new AbstractTab(new ResourceModel("op")) {
+            private static final long serialVersionUID = -6815067322125799251L;
+
+
+            @Override
+            public Panel getPanel(final String panelId) {
+                return new OIDCProvidersDirectoryPanel(panelId, getPageReference());
+            }
+        });
+
+        return tabs;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClientLogin.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClientLogin.java b/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClientLogin.java
new file mode 100644
index 0000000..41b6cdc
--- /dev/null
+++ b/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClientLogin.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.console.pages;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+
+import org.apache.wicket.authentication.IAuthenticationStrategy;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OIDCClientLogin extends WebPage {
+
+    private static final long serialVersionUID = 8581614051773949262L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(OIDCClientLogin.class);
+
+    private static final String OIDC_ACCESS_ERROR = "OIDC access error";
+
+    public OIDCClientLogin(final PageParameters parameters) {
+        super(parameters);
+
+        String token = (String) ((ServletWebRequest) getRequest()).getContainerRequest().
+                getSession().getAttribute(org.apache.syncope.ext.oidcclient.agent.Constants.OIDCCLIENTJWT);
+        if (StringUtils.isBlank(token)) {
+            LOG.error("No JWT found, redirecting to default greeter");
+
+            PageParameters params = new PageParameters();
+            params.add("errorMessage", OIDC_ACCESS_ERROR);
+            setResponsePage(Login.class, params);
+        }
+
+        IAuthenticationStrategy strategy = getApplication().getSecuritySettings().getAuthenticationStrategy();
+
+        if (SyncopeConsoleSession.get().authenticate(token)) {
+
+            // If login has been called because the user was not yet logged in, than continue to the
+            // original destination, otherwise to the Home page
+            continueToOriginalDestination();
+            setResponsePage(getApplication().getHomePage());
+        } else {
+            PageParameters params = new PageParameters();
+            params.add("errorMessage", OIDC_ACCESS_ERROR);
+            setResponsePage(Login.class, params);
+        }
+        strategy.remove();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java b/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java
new file mode 100644
index 0000000..dd66cc6
--- /dev/null
+++ b/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.console.panels;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.DirectoryDataProvider;
+import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+import org.apache.syncope.client.console.layout.UserFormLayoutInfo;
+import org.apache.syncope.client.console.pages.BasePage;
+import org.apache.syncope.client.console.panels.OIDCProvidersDirectoryPanel.OIDCProvidersProvider;
+import org.apache.syncope.client.console.rest.AnyTypeRestClient;
+import org.apache.syncope.client.console.rest.OIDCProviderRestClient;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksTogglePanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.console.wizards.AjaxWizard;
+import org.apache.syncope.client.console.wizards.OIDCProviderWizardBuilder;
+import org.apache.syncope.client.console.wizards.WizardMgtPanel;
+import org.apache.syncope.client.console.wizards.any.AnyWrapper;
+import org.apache.syncope.client.console.wizards.any.UserTemplateWizardBuilder;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.OIDCProviderTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.OIDCClientEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.StringResourceModel;
+
+public class OIDCProvidersDirectoryPanel extends DirectoryPanel<
+        OIDCProviderTO, OIDCProviderTO, OIDCProvidersProvider, OIDCProviderRestClient> {
+
+    private static final long serialVersionUID = -1356497878858616714L;
+
+    private static final String PREF_OIDC_PROVIDERS_PAGINATOR_ROWS = "oidc.providers.paginator.rows";
+
+    private final BaseModal<Serializable> templateModal;
+
+    public OIDCProvidersDirectoryPanel(final String id, final PageReference pageRef) {
+        super(id, new Builder<OIDCProviderTO, OIDCProviderTO, OIDCProviderRestClient>(new OIDCProviderRestClient(),
+                pageRef) {
+
+            private static final long serialVersionUID = -5542535388772406165L;
+
+            @Override
+            protected WizardMgtPanel<OIDCProviderTO> newInstance(final String id, final boolean wizardInModal) {
+                throw new UnsupportedOperationException();
+            }
+        }.disableCheckBoxes());
+
+        this.addNewItemPanelBuilder(new OIDCProviderWizardBuilder(this, new OIDCProviderTO(), pageRef), true);
+        MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, OIDCClientEntitlement.OP_CREATE);
+
+        modal.size(Modal.Size.Large);
+
+        actionTogglePanel = new ActionLinksTogglePanel<OIDCProviderTO>("outer", pageRef) {
+
+            private static final long serialVersionUID = -7688359318035249200L;
+
+            @Override
+            public void toggleWithContent(
+                    final AjaxRequestTarget target,
+                    final ActionsPanel<OIDCProviderTO> actionsPanel,
+                    final OIDCProviderTO modelObject) {
+
+                super.toggleWithContent(target, actionsPanel, modelObject);
+                setHeader(target, StringUtils.abbreviate(modelObject.getName(), 25));
+                this.toggle(target, true);
+            }
+
+        };
+        addOuterObject(actionTogglePanel);
+
+        templateModal = new BaseModal<Serializable>("outer") {
+
+            private static final long serialVersionUID = 5787433530654262016L;
+
+            @Override
+            protected void onConfigure() {
+                super.onConfigure();
+                setFooterVisible(false);
+            }
+        };
+        templateModal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() {
+
+            private static final long serialVersionUID = 8804221891699487139L;
+
+            @Override
+            public void onClose(final AjaxRequestTarget target) {
+//                target.add(content);
+                templateModal.show(false);
+            }
+        });
+        templateModal.size(Modal.Size.Large);
+        addOuterObject(templateModal);
+
+        initResultTable();
+
+    }
+
+    @Override
+    protected OIDCProvidersProvider dataProvider() {
+        return new OIDCProvidersProvider(rows);
+    }
+
+    @Override
+    protected String paginatorRowsKey() {
+        return PREF_OIDC_PROVIDERS_PAGINATOR_ROWS;
+
+    }
+
+    @Override
+    protected List<IColumn<OIDCProviderTO, String>> getColumns() {
+        List<IColumn<OIDCProviderTO, String>> columns = new ArrayList<>();
+        columns.add(new KeyPropertyColumn<OIDCProviderTO>(new ResourceModel("key"), "key", "key"));
+        columns.add(new PropertyColumn<OIDCProviderTO, String>(new ResourceModel("name"), "name", "name"));
+        columns.add(new PropertyColumn<OIDCProviderTO, String>(new ResourceModel("issuer"), "issuer", "issuer"));
+        columns.add(new PropertyColumn<OIDCProviderTO, String>(new ResourceModel("clientID"), "clientID", "clientID"));
+        return columns;
+
+    }
+
+    @Override
+    protected Collection<ActionLink.ActionType> getBulkActions() {
+        return Collections.<ActionLink.ActionType>emptyList();
+
+    }
+
+    @Override
+    public ActionsPanel<OIDCProviderTO> getActions(final IModel<OIDCProviderTO> model) {
+        final ActionsPanel<OIDCProviderTO> panel = super.getActions(model);
+
+        panel.add(new ActionLink<OIDCProviderTO>() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final OIDCProviderTO ignore) {
+                OIDCProviderTO object = restClient.read(model.getObject().getKey());
+                send(OIDCProvidersDirectoryPanel.this, Broadcast.EXACT,
+                        new AjaxWizard.EditItemActionEvent<>(object, target));
+                modal.header(Model.of(StringUtils.capitalize(("Edit " + object.getName()))));
+            }
+        }, ActionLink.ActionType.EDIT, OIDCClientEntitlement.OP_UPDATE);
+
+        panel.add(new ActionLink<OIDCProviderTO>() {
+
+            private static final long serialVersionUID = 8557679125857348178L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final OIDCProviderTO ignore) {
+                final OIDCProviderTO object = restClient.read(model.getObject().getKey());
+
+                UserTemplateWizardBuilder builder = new UserTemplateWizardBuilder(
+                        object.getUserTemplate(),
+                        new AnyTypeRestClient().read(AnyTypeKind.USER.name()).getClasses(),
+                        new UserFormLayoutInfo(),
+                        pageRef) {
+
+                    private static final long serialVersionUID = -7978723352517770634L;
+
+                    @Override
+                    protected Serializable onApplyInternal(final AnyWrapper<UserTO> modelObject) {
+                        object.setUserTemplate(modelObject.getInnerObject());
+                        restClient.update(object);
+
+                        return modelObject;
+                    }
+                };
+                templateModal.header(Model.of(StringUtils.capitalize(
+                        new StringResourceModel("template.title", OIDCProvidersDirectoryPanel.this).getString())));
+                templateModal.setContent(builder.build(BaseModal.CONTENT_ID));
+                templateModal.show(true);
+                target.add(templateModal);
+
+            }
+        }, ActionLink.ActionType.TEMPLATE, OIDCClientEntitlement.OP_UPDATE);
+
+        panel.add(new ActionLink<OIDCProviderTO>() {
+
+            private static final long serialVersionUID = -5467832321897812767L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final OIDCProviderTO ignore) {
+                try {
+                    restClient.delete(model.getObject().getKey());
+                    SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
+                    target.add(container);
+                } catch (SyncopeClientException e) {
+                    LOG.error("While deleting object {}", model.getObject().getKey(), e);
+                    SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage())
+                            ? e.getClass().getName() : e.getMessage());
+                }
+                ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+            }
+        }, ActionLink.ActionType.DELETE, OIDCClientEntitlement.OP_DELETE, true);
+        return panel;
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        super.onEvent(event);
+
+        if (event.getPayload() instanceof AjaxWizard.NewItemEvent) {
+            AjaxWizard.NewItemEvent<?> newItemEvent = AjaxWizard.NewItemEvent.class.cast(event.getPayload());
+            WizardModalPanel<?> modalPanel = newItemEvent.getModalPanel();
+
+            if (event.getPayload() instanceof AjaxWizard.NewItemActionEvent && modalPanel != null) {
+                final IModel<Serializable> model = new CompoundPropertyModel<>(modalPanel.getItem());
+                templateModal.setFormModel(model);
+                templateModal.header(newItemEvent.getResourceModel());
+                newItemEvent.getTarget().add(templateModal.setContent(modalPanel));
+                templateModal.show(true);
+            } else if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent) {
+                templateModal.close(newItemEvent.getTarget());
+            } else if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) {
+                templateModal.close(newItemEvent.getTarget());
+            }
+        }
+    }
+
+    protected final class OIDCProvidersProvider extends DirectoryDataProvider<OIDCProviderTO> {
+
+        private static final long serialVersionUID = -2865055116864423761L;
+
+        private final SortableDataProviderComparator<OIDCProviderTO> comparator;
+
+        public OIDCProvidersProvider(final int paginatorRows) {
+            super(paginatorRows);
+
+            setSort("name", SortOrder.ASCENDING);
+            comparator = new SortableDataProviderComparator<>(this);
+        }
+
+        @Override
+        public Iterator<OIDCProviderTO> iterator(final long first, final long count) {
+            List<OIDCProviderTO> list = restClient.list();
+            Collections.sort(list, comparator);
+            return list.subList((int) first, (int) first + (int) count).iterator();
+        }
+
+        @Override
+        public long size() {
+            return restClient.list().size();
+        }
+
+        @Override
+        public IModel<OIDCProviderTO> model(final OIDCProviderTO object) {
+            return new CompoundPropertyModel<>(object);
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCSSOLoginFormPanel.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCSSOLoginFormPanel.java b/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCSSOLoginFormPanel.java
new file mode 100644
index 0000000..5b108e5
--- /dev/null
+++ b/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCSSOLoginFormPanel.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.console.panels;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.Predicate;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.common.lib.to.OIDCProviderTO;
+import org.apache.syncope.common.rest.api.service.OIDCProviderService;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.markup.html.form.IChoiceRenderer;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.request.UrlUtils;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.http.handler.RedirectRequestHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OIDCSSOLoginFormPanel extends SSOLoginFormPanel {
+
+    private static final long serialVersionUID = 1154933731474854671L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(OIDCSSOLoginFormPanel.class);
+
+    public OIDCSSOLoginFormPanel(final String id) {
+        super(id);
+
+        List<OIDCProviderTO> available =
+                SyncopeConsoleSession.get().getAnonymousClient().getService(OIDCProviderService.class).list();
+
+        final Model<OIDCProviderTO> model = new Model<>();
+        AjaxDropDownChoicePanel<OIDCProviderTO> ops =
+                new AjaxDropDownChoicePanel<>("ops", "OpenID Connect", model, false);
+        ops.setChoices(available);
+        ops.setChoiceRenderer(new IChoiceRenderer<OIDCProviderTO>() {
+
+            private static final long serialVersionUID = 1814750973898916102L;
+
+            @Override
+            public Object getDisplayValue(final OIDCProviderTO object) {
+                return object.getName();
+            }
+
+            @Override
+            public String getIdValue(final OIDCProviderTO object, final int index) {
+                return object.getName();
+            }
+
+            @Override
+            public OIDCProviderTO getObject(final String id,
+                    final IModel<? extends List<? extends OIDCProviderTO>> choices) {
+                return IterableUtils.find(choices.getObject(), new Predicate<OIDCProviderTO>() {
+
+                    @Override
+                    public boolean evaluate(final OIDCProviderTO object) {
+                        return object.getName().equals(id);
+                    }
+                });
+            }
+        });
+
+        ops.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                if (model.getObject() != null) {
+                    try {
+                        RequestCycle.get().scheduleRequestHandlerAfterCurrent(new RedirectRequestHandler(
+                                UrlUtils.rewriteToContextRelative("oidcclient/login?op="
+                                        + URLEncoder.encode(
+                                                model.getObject().getName(), StandardCharsets.UTF_8.name()),
+                                        RequestCycle.get())));
+                    } catch (Exception e) {
+                        LOG.error("Could not redirect to the selected OP {}", model.getObject().getName(), e);
+                    }
+                }
+            }
+        });
+        ops.setOutputMarkupPlaceholderTag(true);
+        ops.setVisible(!available.isEmpty());
+        add(ops);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/rest/OIDCProviderRestClient.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/rest/OIDCProviderRestClient.java b/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/rest/OIDCProviderRestClient.java
new file mode 100644
index 0000000..a666d74
--- /dev/null
+++ b/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/rest/OIDCProviderRestClient.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.console.rest;
+
+import java.util.List;
+import java.util.Set;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.common.lib.to.OIDCProviderTO;
+import org.apache.syncope.common.rest.api.service.OIDCProviderService;
+
+public class OIDCProviderRestClient extends BaseRestClient {
+
+    private static final long serialVersionUID = -4006712447589576324L;
+
+    public List<OIDCProviderTO> list() {
+        return getService(OIDCProviderService.class).list();
+    }
+
+    public Set<String> getActionsClasses() {
+        return getService(OIDCProviderService.class).getActionsClasses();
+    }
+
+    public void create(final OIDCProviderTO op) {
+        SyncopeConsoleSession.get().getService(OIDCProviderService.class).create(op);
+    }
+
+    public void createFromDiscovery(final OIDCProviderTO op) {
+        SyncopeConsoleSession.get().getService(OIDCProviderService.class).createFromDiscovery(op);
+    }
+
+    public OIDCProviderTO read(final String key) {
+        return getService(OIDCProviderService.class).read(key);
+    }
+
+    public void update(final OIDCProviderTO op) {
+        getService(OIDCProviderService.class).update(op);
+    }
+
+    public void delete(final String key) {
+        getService(OIDCProviderService.class).delete(key);
+    }
+
+}