commit
88f14b39a7
131 changed files with 3870 additions and 940 deletions
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
<changeSet author="bburke@redhat.com" id="1.4.0">
|
||||
<delete tableName="CLIENT_SESSION_AUTH_STATUS"/>
|
||||
<delete tableName="CLIENT_SESSION_PROT_MAPPER"/>
|
||||
<delete tableName="CLIENT_SESSION_NOTE"/>
|
||||
<delete tableName="CLIENT_SESSION"/>
|
||||
<delete tableName="USER_SESSION_NOTE"/>
|
||||
<delete tableName="USER_SESSION"/>
|
||||
<createTable tableName="DEFAULT_REQUIRED_ACTIONS">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<addColumn tableName="CLIENT_SESSION">
|
||||
<column name="CURRENT_ACTION" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
<!-- OAUTH_GRANT,
|
||||
CODE_TO_TOKEN,
|
||||
VERIFY_EMAIL,
|
||||
UPDATE_PROFILE,
|
||||
CONFIGURE_TOTP,
|
||||
UPDATE_PASSWORD,
|
||||
RECOVER_PASSWORD,
|
||||
AUTHENTICATE,
|
||||
SOCIAL_CALLBACK,
|
||||
LOGGED_OUT -->
|
||||
<update tableName="CLIENT_SESSION">
|
||||
<column name="CURRENT_ACTION" value="OAUTH_GRANT"/>
|
||||
<where>ACTION = 0</where>
|
||||
</update>
|
||||
<update tableName="CLIENT_SESSION">
|
||||
<column name="CURRENT_ACTION" value="CODE_TO_TOKEN"/>
|
||||
<where>ACTION = 1</where>
|
||||
</update>
|
||||
<update tableName="CLIENT_SESSION">
|
||||
<column name="CURRENT_ACTION" value="VERIFY_EMAIL"/>
|
||||
<where>ACTION = 2</where>
|
||||
</update>
|
||||
<update tableName="CLIENT_SESSION">
|
||||
<column name="CURRENT_ACTION" value="UPDATE_PROFILE"/>
|
||||
<where>ACTION = 3</where>
|
||||
</update>
|
||||
<update tableName="CLIENT_SESSION">
|
||||
<column name="CURRENT_ACTION" value="CONFIGURE_TOTP"/>
|
||||
<where>ACTION = 4</where>
|
||||
</update>
|
||||
<update tableName="CLIENT_SESSION">
|
||||
<column name="CURRENT_ACTION" value="UPDATE_PASSWORD"/>
|
||||
<where>ACTION = 5</where>
|
||||
</update>
|
||||
<update tableName="CLIENT_SESSION">
|
||||
<column name="CURRENT_ACTION" value="RECOVER_PASSWORD"/>
|
||||
<where>ACTION = 6</where>
|
||||
</update>
|
||||
<update tableName="CLIENT_SESSION">
|
||||
<column name="CURRENT_ACTION" value="AUTHENTICATE"/>
|
||||
<where>ACTION = 7</where>
|
||||
</update>
|
||||
<update tableName="CLIENT_SESSION">
|
||||
<column name="CURRENT_ACTION" value="SOCIAL_CALLBACK"/>
|
||||
<where>ACTION = 8</where>
|
||||
</update>
|
||||
<update tableName="CLIENT_SESSION">
|
||||
<column name="CURRENT_ACTION" value="LOGGED_OUT"/>
|
||||
<where>ACTION = 9</where>
|
||||
</update>
|
||||
|
||||
<createTable tableName="CLIENT_USER_SESSION_NOTE">
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="CLIENT_SESSION" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="CLIENT_SESSION, NAME" constraintName="CONSTR_CL_USR_SES_NOTE" tableName="CLIENT_USER_SESSION_NOTE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_USER_SESSION_NOTE" constraintName="FK_CL_USR_SES_NOTE" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
|
||||
<dropColumn tableName="CLIENT_SESSION" columnName="ACTION"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="DEFAULT_REQUIRED_ACTIONS" constraintName="FK_DEF_REQ_ACTS_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -7,4 +7,5 @@
|
|||
<include file="META-INF/jpa-changelog-1.2.0.CR1.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.2.0.Final.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.3.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.4.0.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -12,7 +12,7 @@ public interface JpaUpdaterProvider extends Provider {
|
|||
|
||||
public String FIRST_VERSION = "1.0.0.Final";
|
||||
|
||||
public String LAST_VERSION = "1.3.0.Beta1";
|
||||
public String LAST_VERSION = "1.4.0";
|
||||
|
||||
public String getCurrentVersionSql();
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionAuthStatusEntity</class>
|
||||
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity</class>
|
||||
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity</class>
|
||||
<class>org.keycloak.models.sessions.jpa.entities.ClientUserSessionNoteEntity</class>
|
||||
<class>org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity</class>
|
||||
<class>org.keycloak.models.sessions.jpa.entities.UserSessionEntity</class>
|
||||
<class>org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity</class>
|
||||
|
|
|
@ -25,11 +25,6 @@
|
|||
<listener-class>org.keycloak.services.listeners.KeycloakSessionDestroyListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<filter>
|
||||
<filter-name>Keycloak Client Connection Filter</filter-name>
|
||||
<filter-class>org.keycloak.services.filters.ClientConnectionFilter</filter-class>
|
||||
</filter>
|
||||
|
||||
<filter>
|
||||
<filter-name>Keycloak Session Management</filter-name>
|
||||
<filter-class>org.keycloak.services.filters.KeycloakSessionServletFilter</filter-class>
|
||||
|
|
|
@ -11,6 +11,7 @@ public interface Details {
|
|||
String CODE_ID = "code_id";
|
||||
String REDIRECT_URI = "redirect_uri";
|
||||
String RESPONSE_TYPE = "response_type";
|
||||
String AUTH_TYPE = "auth_type";
|
||||
String AUTH_METHOD = "auth_method";
|
||||
String IDENTITY_PROVIDER = "identity_provider";
|
||||
String IDENTITY_PROVIDER_USERNAME = "identity_provider_identity";
|
||||
|
|
4
events/api/src/main/java/org/keycloak/events/EventBuilder.java
Normal file → Executable file
4
events/api/src/main/java/org/keycloak/events/EventBuilder.java
Normal file → Executable file
|
@ -140,7 +140,9 @@ public class EventBuilder {
|
|||
}
|
||||
|
||||
public void error(String error) {
|
||||
event.setType(EventType.valueOf(event.getType().name() + "_ERROR"));
|
||||
if (!event.getType().name().endsWith("_ERROR")) {
|
||||
event.setType(EventType.valueOf(event.getType().name() + "_ERROR"));
|
||||
}
|
||||
event.setError(error);
|
||||
send();
|
||||
}
|
||||
|
|
|
@ -1045,6 +1045,16 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
},
|
||||
controller : 'ProtocolListCtrl'
|
||||
})
|
||||
.when('/realms/:realm/authentication', {
|
||||
templateUrl : resourceUrl + '/partials/authentication-flows.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
}
|
||||
},
|
||||
controller : 'AuthenticationFlowsCtrl'
|
||||
})
|
||||
|
||||
.when('/server-info', {
|
||||
templateUrl : resourceUrl + '/partials/server-info.html'
|
||||
})
|
||||
|
|
|
@ -1572,6 +1572,45 @@ module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, id
|
|||
|
||||
});
|
||||
|
||||
module.controller('AuthenticationFlowsCtrl', function($scope, realm, AuthenticationExecutions, Notifications, Dialog, $location) {
|
||||
$scope.realm = realm;
|
||||
var setupForm = function() {
|
||||
AuthenticationExecutions.query({realm: realm.realm, alias: 'browser'}, function(data) {
|
||||
$scope.executions = data;
|
||||
$scope.flowmax = 0;
|
||||
for (var i = 0; i < $scope.executions.length; i++ ) {
|
||||
execution = $scope.executions[i];
|
||||
if (execution.requirementChoices.length > $scope.flowmax) {
|
||||
$scope.flowmax = execution.requirementChoices.length;
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < $scope.executions.length; i++ ) {
|
||||
execution = $scope.executions[i];
|
||||
execution.empties = [];
|
||||
for (j = 0; j < $scope.flowmax - execution.requirementChoices.length; j++) {
|
||||
execution.empties.push(j);
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
$scope.updateExecution = function(execution) {
|
||||
var copy = angular.copy(execution);
|
||||
delete copy.empties;
|
||||
AuthenticationExecutions.update({realm: realm.realm, alias: 'browser'}, copy, function() {
|
||||
Notifications.success("Auth requirement updated");
|
||||
setupForm();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
setupForm();
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ module.controller('UserListCtrl', function($scope, realm, User) {
|
|||
|
||||
|
||||
|
||||
module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFederationInstances, $location, Dialog, Notifications) {
|
||||
module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFederationInstances, RequiredActions, $location, Dialog, Notifications) {
|
||||
$scope.realm = realm;
|
||||
$scope.create = !user.id;
|
||||
$scope.editUsername = $scope.create || $scope.realm.editUsernameAllowed;
|
||||
|
@ -219,14 +219,29 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFede
|
|||
}
|
||||
|
||||
$scope.changed = false; // $scope.create;
|
||||
|
||||
if (user.requiredActions) {
|
||||
for (var i = 0; i < user.requiredActions.length; i++) {
|
||||
console.log("user require action: " + user.requiredActions[i]);
|
||||
}
|
||||
}
|
||||
// ID - Name map for required actions. IDs are enum names.
|
||||
$scope.userReqActionList = [
|
||||
RequiredActions.query({id: realm.realm}, function(data) {
|
||||
$scope.userReqActionList = [];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
console.log("listed required action: " + data[i].text);
|
||||
item = { id: data[i].id, text: data[i].text };
|
||||
$scope.userReqActionList.push(item);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/*[
|
||||
{id: "VERIFY_EMAIL", text: "Verify Email"},
|
||||
{id: "UPDATE_PROFILE", text: "Update Profile"},
|
||||
{id: "CONFIGURE_TOTP", text: "Configure Totp"},
|
||||
{id: "UPDATE_PASSWORD", text: "Update Password"}
|
||||
];
|
||||
*/
|
||||
|
||||
$scope.$watch('user', function() {
|
||||
if (!angular.equals($scope.user, user)) {
|
||||
|
|
|
@ -186,6 +186,12 @@ module.factory('RealmAdminEvents', function($resource) {
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('RequiredActions', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:id/required-actions', {
|
||||
id : '@realm'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('RealmLDAPConnectionTester', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/testLDAPConnection');
|
||||
});
|
||||
|
@ -1067,3 +1073,15 @@ module.factory('IdentityProviderMapper', function($resource) {
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('AuthenticationExecutions', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/authentication-flows/flow/:alias/executions', {
|
||||
realm : '@realm',
|
||||
alias : '@alias'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1><strong>Authentication Flows</strong> {{realm.realm|capitalize}}</h1>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<!--
|
||||
<tr>
|
||||
<th class="kc-table-actions" colspan="5">
|
||||
<div class="form-inline">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search..." data-ng-model="search.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-search" type="submit"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
-->
|
||||
<tr data-ng-hide="executions.length == 0">
|
||||
<th colspan="2">Auth Type</th>
|
||||
<th colspan="{{flowmax}}">Requirement</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="execution in executions">
|
||||
<td ng-show="execution.subFlow"></td>
|
||||
<td><h2>{{execution.referenceType}}</h2></td>
|
||||
<td ng-hide="execution.subFlow"></td>
|
||||
<td ng-repeat="choice in execution.requirementChoices">
|
||||
<!--
|
||||
<div class="dropdown pull-left">
|
||||
<select class="form-control"
|
||||
ng-model="execution.requirement"
|
||||
ng-options="choice for choice in execution.requirementChoices">
|
||||
</select>
|
||||
</div>
|
||||
-->
|
||||
<!--
|
||||
<div ng-repeat="choice in execution.requirementChoices">
|
||||
<label >
|
||||
<input type="radio" ng-model="execution.requirement" ng-value="choice">
|
||||
{{choice}}
|
||||
</label>
|
||||
</div>
|
||||
-->
|
||||
<label >
|
||||
<input type="radio" ng-model="execution.requirement" ng-value="choice" ng-change="updateExecution(execution)">
|
||||
{{choice}}
|
||||
</label>
|
||||
|
||||
</td>
|
||||
<td ng-repeat="emptee in execution.empties"></td>
|
||||
</tr>
|
||||
<tr data-ng-show="executions.length == 0">
|
||||
<td>No executions available</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -16,6 +16,7 @@
|
|||
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'roles' || path[2] == 'default-roles' || (path[1] == 'role' && path[3] != 'clients')) && 'active'"><a href="#/realms/{{realm.realm}}/roles">Roles</a></li>
|
||||
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'identity-provider-settings' || path[2] == 'identity-provider-mappers') && 'active'"><a href="#/realms/{{realm.realm}}/identity-provider-settings">Identity Providers</a></li>
|
||||
<li data-ng-show="access.viewRealm" data-ng-class="(path[1] == 'user-federation' || path[2] == 'user-federation') && 'active'"><a href="#/realms/{{realm.realm}}/user-federation">User Federation</a></li>
|
||||
<li data-ng-show="access.viewRealm" data-ng-class="(path[1] == 'authentication' || path[2] == 'authentication') && 'active'"><a href="#/realms/{{realm.realm}}/authentication">Authentication</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
25
forms/common-themes/src/main/resources/theme/base/login/bypass_kerberos.ftl
Executable file
25
forms/common-themes/src/main/resources/theme/base/login/bypass_kerberos.ftl
Executable file
|
@ -0,0 +1,25 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=false; section>
|
||||
<#if section = "title">
|
||||
${msg("kerberosNotConfiguredTitle")}
|
||||
<#elseif section = "header">
|
||||
${msg("kerberosNotConfigured")}
|
||||
<#elseif section = "form">
|
||||
<div id="kc-info-message">
|
||||
<!-- <h3>${msg("kerberosNotConfigured")}</h3> -->
|
||||
<p class="instruction">${msg("bypassKerberosDetail")}</p>
|
||||
<form class="form-actions" action="${url.loginAction}" method="POST">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<div class="${properties.kcFormButtonsWrapperClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="continue" id="kc-login" type="submit" value="${msg("doContinue")}"/>
|
||||
</div>
|
||||
</div>
|
||||
<#if client?? && client.baseUrl?has_content>
|
||||
<p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p>
|
||||
</#if>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -1,76 +1,76 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayInfo=social.displayInfo; section>
|
||||
<#if section = "title">
|
||||
${msg("loginTitle",(realm.name!''))}
|
||||
<#elseif section = "header">
|
||||
${msg("loginTitleHtml",(realm.name!''))}
|
||||
<#elseif section = "form">
|
||||
<#if realm.password>
|
||||
<form id="kc-form-login" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="username" class="${properties.kcLabelClass!}"><#if !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')?html}" type="text" autofocus />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input id="password" class="${properties.kcInputClass!}" name="password" type="password" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<#if realm.rememberMe>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<#if login.rememberMe??>
|
||||
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3" checked> ${msg("rememberMe")}
|
||||
<#else>
|
||||
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> ${msg("rememberMe")}
|
||||
</#if>
|
||||
</label>
|
||||
</div>
|
||||
</#if>
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
<#if realm.resetPasswordAllowed>
|
||||
<span><a href="${url.loginPasswordResetUrl}">${msg("doForgotPassword")}</a></span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<div class="${properties.kcFormButtonsWrapperClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doCancel")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
<#elseif section = "info" >
|
||||
<#if realm.password && realm.registrationAllowed>
|
||||
<div id="kc-registration">
|
||||
<span>${msg("noAccount")} <a href="${url.registrationUrl}">${msg("doRegister")}</a></span>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<#if realm.password && social.providers??>
|
||||
<div id="kc-social-providers">
|
||||
<ul>
|
||||
<#list social.providers as p>
|
||||
<li><a href="${p.loginUrl}" id="zocial-${p.alias}" class="zocial ${p.providerId}"> <span class="text">${p.alias}</span></a></li>
|
||||
</#list>
|
||||
</ul>
|
||||
</div>
|
||||
</#if>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
||||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayInfo=social.displayInfo; section>
|
||||
<#if section = "title">
|
||||
${msg("loginTitle",(realm.name!''))}
|
||||
<#elseif section = "header">
|
||||
${msg("loginTitleHtml",(realm.name!''))}
|
||||
<#elseif section = "form">
|
||||
<#if realm.password>
|
||||
<form id="kc-form-login" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="username" class="${properties.kcLabelClass!}"><#if !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')?html}" type="text" autofocus />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input id="password" class="${properties.kcInputClass!}" name="password" type="password" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<#if realm.rememberMe>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<#if login.rememberMe??>
|
||||
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3" checked> ${msg("rememberMe")}
|
||||
<#else>
|
||||
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> ${msg("rememberMe")}
|
||||
</#if>
|
||||
</label>
|
||||
</div>
|
||||
</#if>
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
<#if realm.resetPasswordAllowed>
|
||||
<span><a href="${url.loginPasswordResetUrl}">${msg("doForgotPassword")}</a></span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<div class="${properties.kcFormButtonsWrapperClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doCancel")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
<#elseif section = "info" >
|
||||
<#if realm.password && realm.registrationAllowed>
|
||||
<div id="kc-registration">
|
||||
<span>${msg("noAccount")} <a href="${url.registrationUrl}">${msg("doRegister")}</a></span>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<#if realm.password && social.providers??>
|
||||
<div id="kc-social-providers">
|
||||
<ul>
|
||||
<#list social.providers as p>
|
||||
<li><a href="${p.loginUrl}" id="zocial-${p.alias}" class="zocial ${p.providerId}"> <span class="text">${p.alias}</span></a></li>
|
||||
</#list>
|
||||
</ul>
|
||||
</div>
|
||||
</#if>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
||||
|
|
|
@ -4,8 +4,15 @@ doCancel=Abbrechen
|
|||
doSubmit=Absenden
|
||||
doYes=Ja
|
||||
doNo=Nein
|
||||
doAccept=Accept
|
||||
doDecline=Decline
|
||||
doContinue=Continue
|
||||
doForgotPassword=Passwort vergessen?
|
||||
doClickHere=hier klicken
|
||||
kerberosNotConfigured=Kerberos Not Configured
|
||||
kerberosNotConfiguredTitle=Kerberos Not Configured
|
||||
bypassKerberos=Your browser is not set up for Kerberos login. Please click continue to login in through other means
|
||||
kerberosNotSetUp=Kerberos is not set up. You cannot login.
|
||||
|
||||
registerWithTitle=Registrierung bei {0}
|
||||
registerWithTitleHtml=Registrierung bei <strong>{0}</strong>
|
||||
|
|
|
@ -4,9 +4,15 @@ doCancel=Cancel
|
|||
doSubmit=Submit
|
||||
doYes=Yes
|
||||
doNo=No
|
||||
doContinue=Continue
|
||||
doAccept=Accept
|
||||
doDecline=Decline
|
||||
doForgotPassword=Forgot Password?
|
||||
doClickHere=Click here
|
||||
|
||||
kerberosNotConfigured=Kerberos Not Configured
|
||||
kerberosNotConfiguredTitle=Kerberos Not Configured
|
||||
bypassKerberosDetail=Either you are not logged in via Kerberos or your browser is not set up for Kerberos login. Please click continue to login in through other means
|
||||
kerberosNotSetUp=Kerberos is not set up. You cannot login.
|
||||
registerWithTitle=Register with {0}
|
||||
registerWithTitleHtml=Register with <strong>{0}</strong>
|
||||
loginTitle=Log in to {0}
|
||||
|
@ -22,6 +28,8 @@ emailForgotTitle=Forgot Your Password?
|
|||
updatePasswordTitle=Update password
|
||||
codeSuccessTitle=Success code
|
||||
codeErrorTitle=Error code\: {0}
|
||||
termsTitle=Terms and Conditions
|
||||
termsTitleHtml=Terms and Conditions
|
||||
|
||||
noAccount=New user?
|
||||
username=Username
|
||||
|
|
|
@ -4,8 +4,15 @@ doCancel=Annulla
|
|||
doSubmit=Invia
|
||||
doYes=Si
|
||||
doNo=No
|
||||
doAccept=Accept
|
||||
doDecline=Decline
|
||||
doContinue=Continue
|
||||
doForgotPassword=Password Dimenticata?
|
||||
doClickHere=Clicca qui
|
||||
bypassKerberos=Your browser is not set up for Kerberos login. Please click continue to login in through other means
|
||||
kerberosNotSetUp=Kerberos is not set up. You cannot login.
|
||||
kerberosNotConfigured=Kerberos Not Configured
|
||||
kerberosNotConfiguredTitle=Kerberos Not Configured
|
||||
|
||||
registerWithTitle=Registrati come {0}
|
||||
registerWithTitleHtml=Registrati come <strong>{0}</strong>
|
||||
|
|
|
@ -3,9 +3,16 @@ doRegister=Cadastre-se
|
|||
doCancel=Cancelar
|
||||
doSubmit=Ok
|
||||
doYes=Sim
|
||||
doAccept=Accept
|
||||
doDecline=Decline
|
||||
doNo=N\u00E3o
|
||||
doContinue=Continue
|
||||
doForgotPassword=Esqueceu sua senha?
|
||||
doClickHere=Clique aqui
|
||||
bypassKerberos=Your browser is not set up for Kerberos login. Please click continue to login in through other means
|
||||
kerberosNotSetUp=Kerberos is not set up. You cannot login.
|
||||
kerberosNotConfigured=Kerberos Not Configured
|
||||
kerberosNotConfiguredTitle=Kerberos Not Configured
|
||||
|
||||
registerWithTitle=Registre-se com {0}
|
||||
registerWithTitleHtml=Registre-se com <strong>{0}</strong>
|
||||
|
|
|
@ -1,77 +1,125 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "title">
|
||||
${msg("registerWithTitle",(realm.name!''))}
|
||||
<#elseif section = "header">
|
||||
${msg("registerWithTitleHtml",(realm.name!''))}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
|
||||
<#if !realm.registrationEmailAsUsername>
|
||||
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="username" class="${properties.kcInputClass!}" name="username" value="${(register.formData.username!'')?html}" />
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName" value="${(register.formData.firstName!'')?html}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName" value="${(register.formData.lastName!'')?html}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="email" class="${properties.kcInputClass!}" name="email" value="${(register.formData.email!'')?html}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password',properties.kcFormGroupErrorClass!)}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="password" id="password" class="${properties.kcInputClass!}" name="password" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password-confirm',properties.kcFormGroupErrorClass!)}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="password" id="password-confirm" class="${properties.kcInputClass!}" name="password-confirm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
<span><a href="${url.loginUrl}">${msg("backToLogin")}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "title">
|
||||
${msg("registerWithTitle",(realm.name!''))}
|
||||
<#elseif section = "header">
|
||||
${msg("registerWithTitleHtml",(realm.name!''))}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
|
||||
<#if !realm.registrationEmailAsUsername>
|
||||
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="username" class="${properties.kcInputClass!}" name="username" value="${(register.formData.username!'')?html}" />
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName" value="${(register.formData.firstName!'')?html}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName" value="${(register.formData.lastName!'')?html}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="email" class="${properties.kcInputClass!}" name="email" value="${(register.formData.email!'')?html}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<#if passwordRequired>
|
||||
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password',properties.kcFormGroupErrorClass!)}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="password" id="password" class="${properties.kcInputClass!}" name="password" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password-confirm',properties.kcFormGroupErrorClass!)}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="password" id="password-confirm" class="${properties.kcInputClass!}" name="password-confirm" />
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
<div class="form-group">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="user.attributes.street" class="${properties.kcLabelClass!}">${msg("street")}</label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.street" name="user.attributes.street"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="user.attributes.locality" class="${properties.kcLabelClass!}">${msg("locality")}</label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.locality" name="user.attributes.locality"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="user.attributes.region" class="${properties.kcLabelClass!}">${msg("region")}</label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.region" name="user.attributes.region"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="user.attributes.postal_code" class="${properties.kcLabelClass!}">${msg("postal_code")}</label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.postal_code" name="user.attributes.postal_code"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="user.attributes.country" class="${properties.kcLabelClass!}">${msg("country")}</label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
<span><a href="${url.loginUrl}">${msg("backToLogin")}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
29
forms/common-themes/src/main/resources/theme/base/login/terms.ftl
Executable file
29
forms/common-themes/src/main/resources/theme/base/login/terms.ftl
Executable file
|
@ -0,0 +1,29 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=false; section>
|
||||
<#if section = "title">
|
||||
${msg("termsTitle")}
|
||||
<#elseif section = "header">
|
||||
${msg("termsTitleHtml")}
|
||||
<#elseif section = "form">
|
||||
<div id="kc-info-message">
|
||||
<textarea class="${properties.kcTextareaClass!}" rows="20" cols="120">
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
</textarea>
|
||||
<form class="form-actions" action="${requiredActionUrl("terms_and_conditions", "")}" method="POST">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<div class="${properties.kcFormButtonsWrapperClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="accept" id="kc-login" type="submit" value="${msg("doAccept")}"/>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doDecline")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -2,6 +2,7 @@ package org.keycloak.login;
|
|||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
@ -24,6 +25,8 @@ public interface LoginFormsProvider extends Provider {
|
|||
|
||||
public Response createResponse(UserModel.RequiredAction action);
|
||||
|
||||
Response createForm(String form, Map<String, Object> attributes);
|
||||
|
||||
public Response createLogin();
|
||||
|
||||
public Response createPasswordReset();
|
||||
|
@ -68,6 +71,8 @@ public interface LoginFormsProvider extends Provider {
|
|||
|
||||
public LoginFormsProvider setFormData(MultivaluedMap<String, String> formData);
|
||||
|
||||
LoginFormsProvider setAttribute(String name, Object value);
|
||||
|
||||
public LoginFormsProvider setStatus(Response.Status status);
|
||||
|
||||
LoginFormsProvider setActionUri(URI requestUri);
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package org.keycloak.login.freemarker;
|
||||
|
||||
import freemarker.template.TemplateMethodModelEx;
|
||||
import freemarker.template.TemplateModelException;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorUtil;
|
||||
import org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.Urls;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class AuthenticatorConfiguredMethod implements TemplateMethodModelEx {
|
||||
private final RealmModel realm;
|
||||
private final UserModel user;
|
||||
private final KeycloakSession session;
|
||||
|
||||
public AuthenticatorConfiguredMethod(RealmModel realm, UserModel user, KeycloakSession session) {
|
||||
this.realm = realm;
|
||||
this.user = user;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object exec(List list) throws TemplateModelException {
|
||||
String providerId = list.get(0).toString();
|
||||
Authenticator authenticator = session.getProvider(Authenticator.class, providerId);
|
||||
return authenticator.configuredFor(session, realm, user);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package org.keycloak.login.freemarker;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailProvider;
|
||||
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
||||
|
@ -27,6 +28,7 @@ import org.keycloak.login.freemarker.model.OAuthGrantBean;
|
|||
import org.keycloak.login.freemarker.model.ProfileBean;
|
||||
import org.keycloak.login.freemarker.model.RealmBean;
|
||||
import org.keycloak.login.freemarker.model.RegisterBean;
|
||||
import org.keycloak.login.freemarker.model.RequiredActionUrlFormatterMethod;
|
||||
import org.keycloak.login.freemarker.model.TotpBean;
|
||||
import org.keycloak.login.freemarker.model.UrlBean;
|
||||
import org.keycloak.models.ClientModel;
|
||||
|
@ -84,6 +86,7 @@ import java.util.concurrent.TimeUnit;
|
|||
private UserModel user;
|
||||
|
||||
private ClientSessionModel clientSession;
|
||||
private final Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
|
||||
public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
|
||||
this.session = session;
|
||||
|
@ -159,8 +162,6 @@ import java.util.concurrent.TimeUnit;
|
|||
uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
|
||||
}
|
||||
|
||||
Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
|
||||
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
|
||||
Theme theme;
|
||||
try {
|
||||
|
@ -205,6 +206,10 @@ import java.util.concurrent.TimeUnit;
|
|||
uriBuilder.replaceQuery(null);
|
||||
}
|
||||
URI baseUri = uriBuilder.build();
|
||||
attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
|
||||
if (realm != null && user != null && session != null) {
|
||||
attributes.put("authenticatorConfigured", new AuthenticatorConfiguredMethod(realm, user, session));
|
||||
}
|
||||
|
||||
if (realm != null) {
|
||||
attributes.put("realm", new RealmBean(realm));
|
||||
|
@ -272,6 +277,105 @@ import java.util.concurrent.TimeUnit;
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response createForm(String form, Map<String, Object> extraAttributes) {
|
||||
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
ClientModel client = session.getContext().getClient();
|
||||
UriInfo uriInfo = session.getContext().getUri();
|
||||
|
||||
MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : new MultivaluedMapImpl<String, String>();
|
||||
|
||||
String requestURI = uriInfo.getBaseUri().getPath();
|
||||
UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
|
||||
|
||||
for (String k : queryParameterMap.keySet()) {
|
||||
|
||||
Object[] objects = queryParameterMap.get(k).toArray();
|
||||
if (objects.length == 1 && objects[0] == null) continue; //
|
||||
uriBuilder.replaceQueryParam(k, objects);
|
||||
}
|
||||
if (accessCode != null) {
|
||||
uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
|
||||
}
|
||||
URI baseUri = uriBuilder.build();
|
||||
|
||||
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
|
||||
Theme theme;
|
||||
try {
|
||||
theme = themeProvider.getTheme(realm.getLoginTheme(), Theme.Type.LOGIN);
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to create theme", e);
|
||||
return Response.serverError().build();
|
||||
}
|
||||
|
||||
try {
|
||||
attributes.put("properties", theme.getProperties());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to load properties", e);
|
||||
}
|
||||
if (client != null) {
|
||||
attributes.put("client", new ClientBean(client));
|
||||
}
|
||||
|
||||
Properties messagesBundle;
|
||||
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, session.getContext().getRequestHeaders());
|
||||
try {
|
||||
messagesBundle = theme.getMessages(locale);
|
||||
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to load messages", e);
|
||||
messagesBundle = new Properties();
|
||||
}
|
||||
|
||||
MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean();
|
||||
if (messages != null) {
|
||||
MessageBean wholeMessage = new MessageBean(null, messageType);
|
||||
for (FormMessage message : this.messages) {
|
||||
String formattedMessageText = formatMessage(message, messagesBundle, locale);
|
||||
if (formattedMessageText != null) {
|
||||
wholeMessage.appendSummaryLine(formattedMessageText);
|
||||
messagesPerField.addMessage(message.getField(), formattedMessageText, messageType);
|
||||
}
|
||||
}
|
||||
attributes.put("message", wholeMessage);
|
||||
}
|
||||
attributes.put("messagesPerField", messagesPerField);
|
||||
|
||||
if (status == null) {
|
||||
status = Response.Status.OK;
|
||||
}
|
||||
|
||||
if (realm != null) {
|
||||
attributes.put("realm", new RealmBean(realm));
|
||||
attributes.put("social", new IdentityProviderBean(realm, baseUri, uriInfo));
|
||||
attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
|
||||
attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
|
||||
|
||||
if (realm.isInternationalizationEnabled()) {
|
||||
UriBuilder b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath());
|
||||
attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
|
||||
}
|
||||
}
|
||||
if (realm != null && user != null && session != null) {
|
||||
attributes.put("authenticatorConfigured", new AuthenticatorConfiguredMethod(realm, user, session));
|
||||
}
|
||||
try {
|
||||
String result = freeMarker.processTemplate(attributes, form, theme);
|
||||
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
|
||||
BrowserSecurityHeaderSetup.headers(builder, realm);
|
||||
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
||||
builder.header(entry.getKey(), entry.getValue());
|
||||
}
|
||||
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, Urls.localeCookiePath(baseUri, realm.getName()));
|
||||
return builder.build();
|
||||
} catch (FreeMarkerException e) {
|
||||
logger.error("Failed to process template", e);
|
||||
return Response.serverError().build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Response createLogin() {
|
||||
return createResponse(LoginFormsPages.LOGIN);
|
||||
}
|
||||
|
@ -299,6 +403,7 @@ import java.util.concurrent.TimeUnit;
|
|||
return createResponse(LoginFormsPages.ERROR);
|
||||
}
|
||||
|
||||
|
||||
public Response createOAuthGrant(ClientSessionModel clientSession) {
|
||||
this.clientSession = clientSession;
|
||||
return createResponse(LoginFormsPages.OAUTH_GRANT);
|
||||
|
@ -381,6 +486,12 @@ import java.util.concurrent.TimeUnit;
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider setAttribute(String name, Object value) {
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider setStatus(Response.Status status) {
|
||||
this.status = status;
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package org.keycloak.login.freemarker.model;
|
||||
|
||||
import freemarker.template.TemplateMethodModelEx;
|
||||
import freemarker.template.TemplateModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.Urls;
|
||||
|
||||
import java.net.URI;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class RequiredActionUrlFormatterMethod implements TemplateMethodModelEx {
|
||||
private final String realm;
|
||||
private final URI baseUri;
|
||||
|
||||
public RequiredActionUrlFormatterMethod(RealmModel realm, URI baseUri) {
|
||||
this.realm = realm.getName();
|
||||
this.baseUri = baseUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object exec(List list) throws TemplateModelException {
|
||||
String action = list.get(0).toString();
|
||||
String relativePath = list.get(1).toString();
|
||||
String url = Urls.requiredActionBase(baseUri).path(relativePath).build(realm, action).toString();
|
||||
return url;
|
||||
}
|
||||
}
|
|
@ -1,102 +1,102 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2012, Red Hat, Inc., and individual contributors
|
||||
* as indicated by the @author tags. See the copyright.txt file in the
|
||||
* distribution for a full listing of individual contributors.
|
||||
*
|
||||
* This is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This software 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this software; if not, write to the Free
|
||||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||
*/
|
||||
package org.keycloak.login.freemarker.model;
|
||||
|
||||
import org.keycloak.freemarker.Theme;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.Urls;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UrlBean {
|
||||
|
||||
private final URI actionuri;
|
||||
private URI baseURI;
|
||||
private Theme theme;
|
||||
private String realm;
|
||||
|
||||
public UrlBean(RealmModel realm, Theme theme, URI baseURI, URI actionUri) {
|
||||
this.realm = realm.getName();
|
||||
this.theme = theme;
|
||||
this.baseURI = baseURI;
|
||||
this.actionuri = actionUri;
|
||||
}
|
||||
|
||||
public String getLoginAction() {
|
||||
if (this.actionuri != null) {
|
||||
return this.actionuri.toString();
|
||||
}
|
||||
return Urls.realmLoginAction(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginUrl() {
|
||||
return Urls.realmLoginPage(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getRegistrationAction() {
|
||||
return Urls.realmRegisterAction(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getRegistrationUrl() {
|
||||
return Urls.realmRegisterPage(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginUpdatePasswordUrl() {
|
||||
return Urls.loginActionUpdatePassword(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginUpdateTotpUrl() {
|
||||
return Urls.loginActionUpdateTotp(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginUpdateProfileUrl() {
|
||||
return Urls.loginActionUpdateProfile(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginPasswordResetUrl() {
|
||||
return Urls.loginPasswordReset(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginUsernameReminderUrl() {
|
||||
return Urls.loginUsernameReminder(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginEmailVerificationUrl() {
|
||||
return Urls.loginActionEmailVerification(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getOauthAction() {
|
||||
if (this.actionuri != null) {
|
||||
return this.actionuri.getPath();
|
||||
}
|
||||
|
||||
return Urls.realmOauthAction(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getResourcesPath() {
|
||||
URI uri = Urls.themeRoot(baseURI);
|
||||
return uri.getPath() + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName();
|
||||
}
|
||||
}
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2012, Red Hat, Inc., and individual contributors
|
||||
* as indicated by the @author tags. See the copyright.txt file in the
|
||||
* distribution for a full listing of individual contributors.
|
||||
*
|
||||
* This is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This software 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this software; if not, write to the Free
|
||||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||
*/
|
||||
package org.keycloak.login.freemarker.model;
|
||||
|
||||
import org.keycloak.freemarker.Theme;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.Urls;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UrlBean {
|
||||
|
||||
private final URI actionuri;
|
||||
private URI baseURI;
|
||||
private Theme theme;
|
||||
private String realm;
|
||||
|
||||
public UrlBean(RealmModel realm, Theme theme, URI baseURI, URI actionUri) {
|
||||
this.realm = realm.getName();
|
||||
this.theme = theme;
|
||||
this.baseURI = baseURI;
|
||||
this.actionuri = actionUri;
|
||||
}
|
||||
|
||||
public String getLoginAction() {
|
||||
if (this.actionuri != null) {
|
||||
return this.actionuri.toString();
|
||||
}
|
||||
return Urls.realmLoginAction(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginUrl() {
|
||||
return Urls.realmLoginPage(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getRegistrationAction() {
|
||||
return Urls.realmRegisterAction(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getRegistrationUrl() {
|
||||
return Urls.realmRegisterPage(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginUpdatePasswordUrl() {
|
||||
return Urls.loginActionUpdatePassword(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginUpdateTotpUrl() {
|
||||
return Urls.loginActionUpdateTotp(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginUpdateProfileUrl() {
|
||||
return Urls.loginActionUpdateProfile(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginPasswordResetUrl() {
|
||||
return Urls.loginPasswordReset(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginUsernameReminderUrl() {
|
||||
return Urls.loginUsernameReminder(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLoginEmailVerificationUrl() {
|
||||
return Urls.loginActionEmailVerification(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getOauthAction() {
|
||||
if (this.actionuri != null) {
|
||||
return this.actionuri.getPath();
|
||||
}
|
||||
|
||||
return Urls.realmOauthAction(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getResourcesPath() {
|
||||
URI uri = Urls.themeRoot(baseURI);
|
||||
return uri.getPath() + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AuthenticationExecutionModel {
|
||||
public static class ExecutionComparator implements Comparator<AuthenticationExecutionModel> {
|
||||
public static final ExecutionComparator SINGLETON = new ExecutionComparator();
|
||||
|
||||
@Override
|
||||
public int compare(AuthenticationExecutionModel o1, AuthenticationExecutionModel o2) {
|
||||
return o1.priority - o2.priority;
|
||||
}
|
||||
}
|
||||
|
||||
private String id;
|
||||
private String authenticator;
|
||||
|
|
|
@ -22,9 +22,9 @@ public interface ClientSessionModel {
|
|||
|
||||
public void setTimestamp(int timestamp);
|
||||
|
||||
public Action getAction();
|
||||
public String getAction();
|
||||
|
||||
public void setAction(Action action);
|
||||
public void setAction(String action);
|
||||
|
||||
public Set<String> getRoles();
|
||||
public void setRoles(Set<String> roles);
|
||||
|
@ -52,6 +52,21 @@ public interface ClientSessionModel {
|
|||
public void setNote(String name, String value);
|
||||
public void removeNote(String name);
|
||||
|
||||
/**
|
||||
* These are notes you want applied to the UserSessionModel when the client session is attached to it.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
*/
|
||||
public void setUserSessionNote(String name, String value);
|
||||
|
||||
/**
|
||||
* These are notes you want applied to the UserSessionModel when the client session is attached to it.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, String> getUserSessionNotes();
|
||||
|
||||
public static enum Action {
|
||||
OAUTH_GRANT,
|
||||
CODE_TO_TOKEN,
|
||||
|
|
6
model/api/src/main/java/org/keycloak/models/KeycloakContext.java
Normal file → Executable file
6
model/api/src/main/java/org/keycloak/models/KeycloakContext.java
Normal file → Executable file
|
@ -1,5 +1,7 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import org.keycloak.ClientConnection;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
|
@ -20,4 +22,8 @@ public interface KeycloakContext {
|
|||
|
||||
void setClient(ClientModel client);
|
||||
|
||||
ClientConnection getConnection();
|
||||
|
||||
void setConnection(ClientConnection connection);
|
||||
|
||||
}
|
||||
|
|
|
@ -156,6 +156,13 @@ public interface RealmModel extends RoleContainerModel {
|
|||
|
||||
void updateDefaultRoles(String[] defaultRoles);
|
||||
|
||||
Set<String> getDefaultRequiredActions();
|
||||
|
||||
void addDefaultRequiredAction(String action);
|
||||
void removeDefaultRequiredAction(String action);
|
||||
|
||||
void setDefaultRequiredActions(Set<String> action);
|
||||
|
||||
// Key is clientId
|
||||
Map<String, ClientModel> getClientNameMap();
|
||||
|
||||
|
@ -180,6 +187,7 @@ public interface RealmModel extends RoleContainerModel {
|
|||
void setSmtpConfig(Map<String, String> smtpConfig);
|
||||
|
||||
List<AuthenticationFlowModel> getAuthenticationFlows();
|
||||
AuthenticationFlowModel getFlowByAlias(String alias);
|
||||
AuthenticationFlowModel addAuthenticationFlow(AuthenticationFlowModel model);
|
||||
AuthenticationFlowModel getAuthenticationFlowById(String id);
|
||||
void removeAuthenticationFlow(AuthenticationFlowModel model);
|
||||
|
|
|
@ -28,8 +28,8 @@ public class UserFederationManager implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles) {
|
||||
UserModel user = session.userStorage().addUser(realm, id, username.toLowerCase(), addDefaultRoles);
|
||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||
UserModel user = session.userStorage().addUser(realm, id, username.toLowerCase(), addDefaultRoles, addDefaultRequiredActions);
|
||||
return registerWithFederation(realm, user);
|
||||
}
|
||||
|
||||
|
@ -386,6 +386,25 @@ public class UserFederationManager implements UserProvider {
|
|||
return session.userStorage().validCredentials(realm, user, input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the user configured to use this credential type
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean configuredForCredentialType(String type, RealmModel realm, UserModel user) {
|
||||
UserFederationProvider link = getFederationLink(realm, user);
|
||||
if (link != null) {
|
||||
Set<String> supportedCredentialTypes = link.getSupportedCredentialTypes(user);
|
||||
if (supportedCredentialTypes.contains(type)) return true;
|
||||
}
|
||||
List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
|
||||
for (UserCredentialValueModel cred : creds) {
|
||||
if (cred.getType().equals(type)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
|
||||
return validCredentials(realm, user, Arrays.asList(input));
|
||||
|
|
|
@ -69,14 +69,6 @@ public interface UserModel {
|
|||
|
||||
void updateCredentialDirectly(UserCredentialValueModel cred);
|
||||
|
||||
/**
|
||||
* Is the use configured to use this credential type
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
boolean configuredForCredentialType(String type);
|
||||
|
||||
Set<RoleModel> getRealmRoleMappings();
|
||||
Set<RoleModel> getClientRoleMappings(ClientModel app);
|
||||
boolean hasRole(RoleModel role);
|
||||
|
|
|
@ -13,7 +13,7 @@ import java.util.Set;
|
|||
public interface UserProvider extends Provider {
|
||||
// Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession
|
||||
|
||||
UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles);
|
||||
UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions);
|
||||
UserModel addUser(RealmModel realm, String username);
|
||||
boolean removeUser(RealmModel realm, UserModel user);
|
||||
|
||||
|
|
|
@ -38,10 +38,12 @@ public interface UserSessionModel {
|
|||
List<ClientSessionModel> getClientSessions();
|
||||
|
||||
public static enum AuthenticatorStatus {
|
||||
FAILED,
|
||||
SUCCESS,
|
||||
SETUP_REQUIRED,
|
||||
ATTEMPTED,
|
||||
SKIPPED
|
||||
SKIPPED,
|
||||
CHALLENGED
|
||||
}
|
||||
|
||||
public String getNote(String name);
|
||||
|
|
|
@ -2,8 +2,10 @@ package org.keycloak.models.entities;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -76,6 +78,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
|
|||
private List<IdentityProviderMapperEntity> identityProviderMappers = new ArrayList<IdentityProviderMapperEntity>();
|
||||
private List<AuthenticationFlowEntity> authenticationFlows = new ArrayList<>();
|
||||
private List<AuthenticatorEntity> authenticators = new ArrayList<>();
|
||||
private List<String> defaultRequiredActions = new ArrayList<>();
|
||||
|
||||
|
||||
public String getName() {
|
||||
|
@ -500,6 +503,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
|
|||
public void setAuthenticators(List<AuthenticatorEntity> authenticators) {
|
||||
this.authenticators = authenticators;
|
||||
}
|
||||
|
||||
public List<String> getDefaultRequiredActions() {
|
||||
return defaultRequiredActions;
|
||||
}
|
||||
|
||||
public void setDefaultRequiredActions(List<String> defaultRequiredActions) {
|
||||
this.defaultRequiredActions = defaultRequiredActions;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -10,6 +10,10 @@ import org.keycloak.models.RealmModel;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class DefaultAuthenticationFlows {
|
||||
|
||||
public static final String BROWSER_FLOW = "browser";
|
||||
public static final String FORMS_FLOW = "forms";
|
||||
|
||||
public static void addFlows(RealmModel realm) {
|
||||
AuthenticatorModel model = new AuthenticatorModel();
|
||||
model.setProviderId("auth-cookie");
|
||||
|
@ -31,9 +35,13 @@ public class DefaultAuthenticationFlows {
|
|||
model.setProviderId("auth-otp-form");
|
||||
model.setAlias("Single OTP Form");
|
||||
AuthenticatorModel otp = realm.addAuthenticator(model);
|
||||
model = new AuthenticatorModel();
|
||||
model.setProviderId("auth-spnego");
|
||||
model.setAlias("Kerberos");
|
||||
AuthenticatorModel kerberos = realm.addAuthenticator(model);
|
||||
|
||||
AuthenticationFlowModel browser = new AuthenticationFlowModel();
|
||||
browser.setAlias("browser");
|
||||
browser.setAlias(BROWSER_FLOW);
|
||||
browser.setDescription("browser based authentication");
|
||||
browser = realm.addAuthenticationFlow(browser);
|
||||
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
|
||||
|
@ -44,15 +52,23 @@ public class DefaultAuthenticationFlows {
|
|||
execution.setUserSetupAllowed(false);
|
||||
execution.setAutheticatorFlow(false);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(browser.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED);
|
||||
execution.setAuthenticator(kerberos.getId());
|
||||
execution.setPriority(1);
|
||||
execution.setUserSetupAllowed(false);
|
||||
execution.setAutheticatorFlow(false);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
AuthenticationFlowModel forms = new AuthenticationFlowModel();
|
||||
forms.setAlias("forms");
|
||||
forms.setAlias(FORMS_FLOW);
|
||||
forms.setDescription("Username, password, otp and other auth forms.");
|
||||
forms = realm.addAuthenticationFlow(forms);
|
||||
execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(browser.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
|
||||
execution.setAuthenticator(forms.getId());
|
||||
execution.setPriority(1);
|
||||
execution.setPriority(2);
|
||||
execution.setUserSetupAllowed(false);
|
||||
execution.setAutheticatorFlow(true);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
@ -74,7 +90,7 @@ public class DefaultAuthenticationFlows {
|
|||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
execution.setAuthenticator(password.getId());
|
||||
execution.setPriority(11);
|
||||
execution.setUserSetupAllowed(false);
|
||||
execution.setUserSetupAllowed(true);
|
||||
execution.setAutheticatorFlow(false);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
|
@ -88,5 +104,7 @@ public class DefaultAuthenticationFlows {
|
|||
execution.setAutheticatorFlow(false);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
//
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,8 @@ public class ModelToRepresentation {
|
|||
rep.setFederationLink(user.getFederationLink());
|
||||
|
||||
List<String> reqActions = new ArrayList<String>();
|
||||
for (String ra : user.getRequiredActions()){
|
||||
Set<String> requiredActions = user.getRequiredActions();
|
||||
for (String ra : requiredActions){
|
||||
reqActions.add(ra);
|
||||
}
|
||||
|
||||
|
|
|
@ -796,7 +796,7 @@ public class RepresentationToModel {
|
|||
convertDeprecatedSocialProviders(userRep);
|
||||
|
||||
// Import users just to user storage. Don't federate
|
||||
UserModel user = session.userStorage().addUser(newRealm, userRep.getId(), userRep.getUsername(), false);
|
||||
UserModel user = session.userStorage().addUser(newRealm, userRep.getId(), userRep.getUsername(), false, false);
|
||||
user.setEnabled(userRep.isEnabled());
|
||||
user.setEmail(userRep.getEmail());
|
||||
user.setEmailVerified(userRep.isEmailVerified());
|
||||
|
|
|
@ -87,11 +87,6 @@ public class UserModelDelegate implements UserModel {
|
|||
delegate.removeRequiredAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredForCredentialType(String type) {
|
||||
return delegate.configuredForCredentialType(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(RequiredAction action) {
|
||||
delegate.addRequiredAction(action);
|
||||
|
|
|
@ -266,7 +266,7 @@ public class FileUserProvider implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public UserAdapter addUser(RealmModel realm, String id, String username, boolean addDefaultRoles) {
|
||||
public UserAdapter addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||
if (inMemoryModel.hasUserWithUsername(realm.getId(), username.toLowerCase()))
|
||||
throw new ModelDuplicateException("User with username " + username + " already exists in realm.");
|
||||
|
||||
|
@ -284,6 +284,13 @@ public class FileUserProvider implements UserProvider {
|
|||
}
|
||||
}
|
||||
|
||||
if (addDefaultRequiredActions) {
|
||||
for (String r : realm.getDefaultRequiredActions()) {
|
||||
userModel.addRequiredAction(r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return userModel;
|
||||
}
|
||||
|
||||
|
@ -358,7 +365,7 @@ public class FileUserProvider implements UserProvider {
|
|||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String username) {
|
||||
return this.addUser(realm, KeycloakModelUtils.generateId(), username.toLowerCase(), true);
|
||||
return this.addUser(realm, KeycloakModelUtils.generateId(), username.toLowerCase(), true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1213,6 +1213,19 @@ public class RealmAdapter implements RealmModel {
|
|||
return models;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public AuthenticationFlowModel getFlowByAlias(String alias) {
|
||||
for (AuthenticationFlowModel flow : getAuthenticationFlows()) {
|
||||
if (flow.getAlias().equals(alias)) {
|
||||
return flow;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected AuthenticationFlowModel entityToModel(AuthenticationFlowEntity entity) {
|
||||
AuthenticationFlowModel model = new AuthenticationFlowModel();
|
||||
model.setId(entity.getId());
|
||||
|
@ -1276,6 +1289,7 @@ public class RealmAdapter implements RealmModel {
|
|||
AuthenticationExecutionModel model = entityToModel(entity);
|
||||
executions.add(model);
|
||||
}
|
||||
Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
|
||||
return executions;
|
||||
}
|
||||
|
||||
|
@ -1548,4 +1562,37 @@ public class RealmAdapter implements RealmModel {
|
|||
mapper.setConfig(config);
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDefaultRequiredActions() {
|
||||
Set<String> result = new HashSet<String>();
|
||||
if (realm.getDefaultRequiredActions() != null) {
|
||||
result.addAll(realm.getDefaultRequiredActions());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDefaultRequiredAction(String action) {
|
||||
Set<String> actions = getDefaultRequiredActions();
|
||||
actions.add(action);
|
||||
setDefaultRequiredActions(actions);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDefaultRequiredAction(String action) {
|
||||
Set<String> actions = getDefaultRequiredActions();
|
||||
actions.remove(action);
|
||||
setDefaultRequiredActions(actions);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultRequiredActions(Set<String> action) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
result.addAll(action);
|
||||
realm.setDefaultRequiredActions(result);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -220,17 +220,6 @@ public class UserAdapter implements UserModel, Comparable {
|
|||
user.setRequiredActions(requiredActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredForCredentialType(String type) {
|
||||
List<UserCredentialValueModel> creds = getCredentialsDirectly();
|
||||
for (UserCredentialValueModel cred : creds) {
|
||||
if (cred.getType().equals(type)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isTotp() {
|
||||
return user.isTotp();
|
||||
|
|
|
@ -183,7 +183,6 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
if (cached == null) {
|
||||
UserModel model = getDelegate().getUserByEmail(email, realm);
|
||||
if (model == null) return null;
|
||||
if (managedUsers.containsKey(model.getId())) return managedUsers.get(model.getId());
|
||||
if (userInvalidations.containsKey(model.getId())) return model;
|
||||
cached = new CachedUser(realm, model);
|
||||
cache.addCachedUser(realm.getId(), cached);
|
||||
|
@ -253,8 +252,8 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles) {
|
||||
UserModel user = getDelegate().addUser(realm, id, username, addDefaultRoles);
|
||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||
UserModel user = getDelegate().addUser(realm, id, username, addDefaultRoles, addDefaultRoles);
|
||||
managedUsers.put(user.getId(), user);
|
||||
return user;
|
||||
}
|
||||
|
|
|
@ -119,8 +119,8 @@ public class NoCacheUserProvider implements CacheUserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles) {
|
||||
return getDelegate().addUser(realm, id, username, addDefaultRoles);
|
||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||
return getDelegate().addUser(realm, id, username, addDefaultRoles, addDefaultRequiredActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1024,6 +1024,16 @@ public class RealmAdapter implements RealmModel {
|
|||
return models;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationFlowModel getFlowByAlias(String alias) {
|
||||
for (AuthenticationFlowModel flow : getAuthenticationFlows()) {
|
||||
if (flow.getAlias().equals(alias)) {
|
||||
return flow;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationFlowModel addAuthenticationFlow(AuthenticationFlowModel model) {
|
||||
getDelegateForUpdate();
|
||||
|
@ -1116,4 +1126,30 @@ public class RealmAdapter implements RealmModel {
|
|||
if (updated != null) return updated.getAuthenticatorById(id);
|
||||
return cached.getAuthenticators().get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDefaultRequiredActions() {
|
||||
return cached.getDefaultRequiredActions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDefaultRequiredAction(String action) {
|
||||
getDelegateForUpdate();
|
||||
updated.addDefaultRequiredAction(action);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDefaultRequiredAction(String action) {
|
||||
getDelegateForUpdate();
|
||||
updated.removeDefaultRequiredAction(action);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultRequiredActions(Set<String> action) {
|
||||
getDelegateForUpdate();
|
||||
updated.setDefaultRequiredActions(action);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,15 +131,6 @@ public class UserAdapter implements UserModel {
|
|||
updated.removeRequiredAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredForCredentialType(String type) {
|
||||
List<UserCredentialValueModel> creds = getCredentialsDirectly();
|
||||
for (UserCredentialValueModel cred : creds) {
|
||||
if (cred.getType().equals(type)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstName() {
|
||||
if (updated != null) return updated.getFirstName();
|
||||
|
|
|
@ -98,6 +98,7 @@ public class CachedRealm {
|
|||
private Set<String> supportedLocales = new HashSet<String>();
|
||||
private String defaultLocale;
|
||||
private MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
|
||||
private Set<String> defaultRequiredActions = new HashSet<>();
|
||||
|
||||
public CachedRealm() {
|
||||
}
|
||||
|
@ -200,6 +201,7 @@ public class CachedRealm {
|
|||
for (AuthenticatorModel authenticator : model.getAuthenticators()) {
|
||||
authenticators.put(authenticator.getId(), authenticator);
|
||||
}
|
||||
this.defaultRequiredActions.addAll(model.getDefaultRequiredActions());
|
||||
|
||||
}
|
||||
|
||||
|
@ -438,4 +440,8 @@ public class CachedRealm {
|
|||
public Map<String, AuthenticationExecutionModel> getExecutionsById() {
|
||||
return executionsById;
|
||||
}
|
||||
|
||||
public Set<String> getDefaultRequiredActions() {
|
||||
return defaultRequiredActions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles) {
|
||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||
if (id == null) {
|
||||
id = KeycloakModelUtils.generateId();
|
||||
}
|
||||
|
@ -68,13 +68,18 @@ public class JpaUserProvider implements UserProvider {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (addDefaultRequiredActions) {
|
||||
for (String r : realm.getDefaultRequiredActions()) {
|
||||
userModel.addRequiredAction(r);
|
||||
}
|
||||
}
|
||||
|
||||
return userModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String username) {
|
||||
return addUser(realm, KeycloakModelUtils.generateId(), username.toLowerCase(), true);
|
||||
return addUser(realm, KeycloakModelUtils.generateId(), username.toLowerCase(), true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1525,6 +1525,17 @@ public class RealmAdapter implements RealmModel {
|
|||
return models;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationFlowModel getFlowByAlias(String alias) {
|
||||
for (AuthenticationFlowModel flow : getAuthenticationFlows()) {
|
||||
if (flow.getAlias().equals(alias)) {
|
||||
return flow;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected AuthenticationFlowModel entityToModel(AuthenticationFlowEntity entity) {
|
||||
AuthenticationFlowModel model = new AuthenticationFlowModel();
|
||||
model.setId(entity.getId());
|
||||
|
@ -1583,6 +1594,7 @@ public class RealmAdapter implements RealmModel {
|
|||
AuthenticationExecutionModel model = entityToModel(entity);
|
||||
executions.add(model);
|
||||
}
|
||||
Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
|
||||
return executions;
|
||||
}
|
||||
|
||||
|
@ -1713,5 +1725,30 @@ public class RealmAdapter implements RealmModel {
|
|||
return authenticators;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDefaultRequiredActions() {
|
||||
Set<String> result = new HashSet<String>();
|
||||
result.addAll(realm.getDefaultRequiredActions());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setDefaultRequiredActions(Set<String> actions) {
|
||||
realm.setDefaultRequiredActions(actions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDefaultRequiredAction(String action) {
|
||||
realm.getDefaultRequiredActions().add(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDefaultRequiredAction(String action) {
|
||||
realm.getDefaultRequiredActions().remove(action);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -185,15 +185,6 @@ public class UserAdapter implements UserModel {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredForCredentialType(String type) {
|
||||
List<UserCredentialValueModel> creds = getCredentialsDirectly();
|
||||
for (UserCredentialValueModel cred : creds) {
|
||||
if (cred.getType().equals(type)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstName() {
|
||||
return user.getFirstName();
|
||||
|
|
|
@ -113,6 +113,12 @@ public class RealmEntity {
|
|||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
|
||||
|
||||
@ElementCollection
|
||||
@Column(name="VALUE")
|
||||
@CollectionTable(name = "DEFAULT_REQUIRED_ACTIONS", joinColumns={ @JoinColumn(name="REALM_ID") })
|
||||
protected Set<String> defaultRequiredActions = new HashSet<String>();
|
||||
|
||||
|
||||
@ElementCollection
|
||||
@MapKeyColumn(name="NAME")
|
||||
@Column(name="VALUE")
|
||||
|
@ -568,5 +574,13 @@ public class RealmEntity {
|
|||
public void setAuthenticationFlows(Collection<AuthenticationFlowEntity> authenticationFlows) {
|
||||
this.authenticationFlows = authenticationFlows;
|
||||
}
|
||||
|
||||
public Set<String> getDefaultRequiredActions() {
|
||||
return defaultRequiredActions;
|
||||
}
|
||||
|
||||
public void setDefaultRequiredActions(Set<String> defaultRequiredActions) {
|
||||
this.defaultRequiredActions = defaultRequiredActions;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -242,7 +242,7 @@ public class MongoUserProvider implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public UserAdapter addUser(RealmModel realm, String id, String username, boolean addDefaultRoles) {
|
||||
public UserAdapter addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||
UserAdapter userModel = addUserEntity(realm, id, username.toLowerCase());
|
||||
|
||||
if (addDefaultRoles) {
|
||||
|
@ -257,6 +257,13 @@ public class MongoUserProvider implements UserProvider {
|
|||
}
|
||||
}
|
||||
|
||||
if (addDefaultRequiredActions) {
|
||||
for (String r : realm.getDefaultRequiredActions()) {
|
||||
userModel.addRequiredAction(r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return userModel;
|
||||
}
|
||||
|
||||
|
@ -327,7 +334,7 @@ public class MongoUserProvider implements UserProvider {
|
|||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String username) {
|
||||
return this.addUser(realm, null, username, true);
|
||||
return this.addUser(realm, null, username, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1290,6 +1290,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
return models;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationFlowModel getFlowByAlias(String alias) {
|
||||
for (AuthenticationFlowModel flow : getAuthenticationFlows()) {
|
||||
if (flow.getAlias().equals(alias)) {
|
||||
return flow;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected AuthenticationFlowModel entityToModel(AuthenticationFlowEntity entity) {
|
||||
AuthenticationFlowModel model = new AuthenticationFlowModel();
|
||||
model.setId(entity.getId());
|
||||
|
@ -1354,6 +1365,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
AuthenticationExecutionModel model = entityToModel(entity);
|
||||
executions.add(model);
|
||||
}
|
||||
Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
|
||||
return executions;
|
||||
}
|
||||
|
||||
|
@ -1636,4 +1648,32 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
mapper.setConfig(config);
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDefaultRequiredActions() {
|
||||
Set<String> result = new HashSet<String>();
|
||||
result.addAll(realm.getDefaultRequiredActions());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setDefaultRequiredActions(Set<String> actions) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
result.addAll(actions);
|
||||
getMongoEntity().setDefaultRequiredActions(result);
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDefaultRequiredAction(String action) {
|
||||
getMongoStore().pushItemToList(getMongoEntity(), "defaultRequiredActions", action, true, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDefaultRequiredAction(String action) {
|
||||
getMongoStore().pullItemFromList(getMongoEntity(), "defaultRequiredActions", action, invocationContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -333,16 +333,6 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredForCredentialType(String type) {
|
||||
List<UserCredentialValueModel> creds = getCredentialsDirectly();
|
||||
for (UserCredentialValueModel cred : creds) {
|
||||
if (cred.getType().equals(type)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void updateCredentialDirectly(UserCredentialValueModel credModel) {
|
||||
CredentialEntity credentialEntity = getCredentialEntity(user, credModel.getType());
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.keycloak.models.UserSessionModel;
|
|||
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -99,12 +100,12 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Action getAction() {
|
||||
public String getAction() {
|
||||
return entity.getAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAction(Action action) {
|
||||
public void setAction(String action) {
|
||||
entity.setAction(action);
|
||||
update();
|
||||
}
|
||||
|
@ -164,6 +165,26 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserSessionNote(String name, String value) {
|
||||
if (entity.getUserSessionNotes() == null) {
|
||||
entity.setUserSessionNotes(new HashMap<String, String>());
|
||||
}
|
||||
entity.getNotes().put(name, value);
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getUserSessionNotes() {
|
||||
if (entity.getUserSessionNotes() == null) {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
HashMap<String, String> copy = new HashMap<>();
|
||||
copy.putAll(entity.getUserSessionNotes());
|
||||
return copy;
|
||||
}
|
||||
|
||||
void update() {
|
||||
provider.getTx().replace(cache, entity.getId(), entity);
|
||||
}
|
||||
|
|
|
@ -24,11 +24,12 @@ public class ClientSessionEntity extends SessionEntity {
|
|||
|
||||
private int timestamp;
|
||||
|
||||
private ClientSessionModel.Action action;
|
||||
private String action;
|
||||
|
||||
private Set<String> roles;
|
||||
private Set<String> protocolMappers;
|
||||
private Map<String, String> notes;
|
||||
private Map<String, String> userSessionNotes;
|
||||
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
|
||||
private String authUserId;
|
||||
|
||||
|
@ -80,11 +81,11 @@ public class ClientSessionEntity extends SessionEntity {
|
|||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public ClientSessionModel.Action getAction() {
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(ClientSessionModel.Action action) {
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
|
@ -127,4 +128,12 @@ public class ClientSessionEntity extends SessionEntity {
|
|||
public void setAuthUserId(String authUserId) {
|
||||
this.authUserId = authUserId;
|
||||
}
|
||||
|
||||
public Map<String, String> getUserSessionNotes() {
|
||||
return userSessionNotes;
|
||||
}
|
||||
|
||||
public void setUserSessionNotes(Map<String, String> userSessionNotes) {
|
||||
this.userSessionNotes = userSessionNotes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,11 @@ import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
|
|||
import org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity;
|
||||
import org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity;
|
||||
import org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity;
|
||||
import org.keycloak.models.sessions.jpa.entities.ClientUserSessionNoteEntity;
|
||||
import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
@ -78,6 +80,32 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserSessionNote(String name, String value) {
|
||||
for (ClientUserSessionNoteEntity attr : entity.getUserSessionNotes()) {
|
||||
if (attr.getName().equals(name)) {
|
||||
attr.setValue(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ClientUserSessionNoteEntity attr = new ClientUserSessionNoteEntity();
|
||||
attr.setName(name);
|
||||
attr.setValue(value);
|
||||
attr.setClientSession(entity);
|
||||
em.persist(attr);
|
||||
entity.getUserSessionNotes().add(attr);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getUserSessionNotes() {
|
||||
Map<String, String> copy = new HashMap<>();
|
||||
for (ClientUserSessionNoteEntity attr : entity.getUserSessionNotes()) {
|
||||
copy.put(attr.getName(), attr.getValue());
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return entity.getId();
|
||||
|
@ -161,12 +189,12 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Action getAction() {
|
||||
public String getAction() {
|
||||
return entity.getAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAction(Action action) {
|
||||
public void setAction(String action) {
|
||||
entity.setAction(action);
|
||||
}
|
||||
|
||||
|
|
|
@ -54,8 +54,8 @@ public class ClientSessionEntity {
|
|||
@Column(name="AUTH_METHOD")
|
||||
protected String authMethod;
|
||||
|
||||
@Column(name="ACTION")
|
||||
protected ClientSessionModel.Action action;
|
||||
@Column(name="CURRENT_ACTION")
|
||||
protected String action;
|
||||
|
||||
@Column(name="AUTH_USER_ID")
|
||||
protected String userId;
|
||||
|
@ -69,6 +69,9 @@ public class ClientSessionEntity {
|
|||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
|
||||
protected Collection<ClientSessionNoteEntity> notes = new ArrayList<ClientSessionNoteEntity>();
|
||||
|
||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
|
||||
protected Collection<ClientUserSessionNoteEntity> userSessionNotes = new ArrayList<>();
|
||||
|
||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
|
||||
protected Collection<ClientSessionAuthStatusEntity> authanticatorStatus = new ArrayList<>();
|
||||
|
||||
|
@ -120,11 +123,11 @@ public class ClientSessionEntity {
|
|||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public ClientSessionModel.Action getAction() {
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(ClientSessionModel.Action action) {
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
|
@ -175,4 +178,12 @@ public class ClientSessionEntity {
|
|||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Collection<ClientUserSessionNoteEntity> getUserSessionNotes() {
|
||||
return userSessionNotes;
|
||||
}
|
||||
|
||||
public void setUserSessionNotes(Collection<ClientUserSessionNoteEntity> userSessionNotes) {
|
||||
this.userSessionNotes = userSessionNotes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
package org.keycloak.models.sessions.jpa.entities;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "removeClientUserSessionNoteByUser", query="delete from ClientUserSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId))"),
|
||||
@NamedQuery(name = "removeClientUserSessionNoteByClient", query="delete from ClientUserSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.clientId = :clientId and c.realmId = :realmId)"),
|
||||
@NamedQuery(name = "removeClientUserSessionNoteByRealm", query="delete from ClientUserSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.realmId = :realmId)"),
|
||||
@NamedQuery(name = "removeClientUserSessionNoteByExpired", query = "delete from ClientUserSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime)))"),
|
||||
@NamedQuery(name = "removeDetachedUserClientSessionNoteByExpired", query = "delete from ClientUserSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IS NULL and c.realmId = :realmId and c.timestamp < :maxTime )")
|
||||
})
|
||||
@Table(name="CLIENT_USER_SESSION_NOTE")
|
||||
@Entity
|
||||
@IdClass(ClientUserSessionNoteEntity.Key.class)
|
||||
public class ClientUserSessionNoteEntity {
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
@JoinColumn(name = "CLIENT_SESSION")
|
||||
protected ClientSessionEntity clientSession;
|
||||
|
||||
@Id
|
||||
@Column(name = "NAME")
|
||||
protected String name;
|
||||
@Column(name = "VALUE")
|
||||
protected String value;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public ClientSessionEntity getClientSession() {
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
public void setClientSession(ClientSessionEntity clientSession) {
|
||||
this.clientSession = clientSession;
|
||||
}
|
||||
|
||||
public static class Key implements Serializable {
|
||||
|
||||
protected ClientSessionEntity clientSession;
|
||||
|
||||
protected String name;
|
||||
|
||||
public Key() {
|
||||
}
|
||||
|
||||
public Key(ClientSessionEntity clientSession, String name) {
|
||||
this.clientSession = clientSession;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public ClientSessionEntity getClientSession() {
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Key key = (Key) o;
|
||||
|
||||
if (name != null ? !name.equals(key.name) : key.name != null) return false;
|
||||
if (clientSession != null ? !clientSession.getId().equals(key.clientSession != null ? key.clientSession.getId() : null) : key.clientSession != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = clientSession != null ? clientSession.getId().hashCode() : 0;
|
||||
result = 31 * result + (name != null ? name.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -93,12 +93,12 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel.Action getAction() {
|
||||
public String getAction() {
|
||||
return entity.getAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAction(ClientSessionModel.Action action) {
|
||||
public void setAction(String action) {
|
||||
entity.setAction(action);
|
||||
}
|
||||
|
||||
|
@ -134,6 +134,16 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserSessionNote(String name, String value) {
|
||||
entity.getUserSessionNotes().put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getUserSessionNotes() {
|
||||
return entity.getUserSessionNotes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthMethod() {
|
||||
return entity.getAuthMethod();
|
||||
|
|
|
@ -24,10 +24,11 @@ public class ClientSessionEntity {
|
|||
private String authMethod;
|
||||
|
||||
private int timestamp;
|
||||
private ClientSessionModel.Action action;
|
||||
private String action;
|
||||
private Set<String> roles;
|
||||
private Set<String> protocolMappers;
|
||||
private Map<String, String> notes = new HashMap<>();
|
||||
private Map<String, String> userSessionNotes = new HashMap<>();
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
@ -77,11 +78,11 @@ public class ClientSessionEntity {
|
|||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public ClientSessionModel.Action getAction() {
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(ClientSessionModel.Action action) {
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
|
@ -128,4 +129,8 @@ public class ClientSessionEntity {
|
|||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
|
||||
this.authenticatorStatus = authenticatorStatus;
|
||||
}
|
||||
|
||||
public Map<String, String> getUserSessionNotes() {
|
||||
return userSessionNotes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.keycloak.models.UserSessionModel;
|
|||
import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity;
|
||||
import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -107,12 +108,12 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
|
|||
}
|
||||
|
||||
@Override
|
||||
public Action getAction() {
|
||||
public String getAction() {
|
||||
return entity.getAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAction(Action action) {
|
||||
public void setAction(String action) {
|
||||
entity.setAction(action);
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
@ -156,6 +157,19 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
|
|||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserSessionNote(String name, String value) {
|
||||
entity.getUserSessionNotes().put(name, value);
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getUserSessionNotes() {
|
||||
Map<String, String> copy = new HashMap<>();
|
||||
copy.putAll(entity.getUserSessionNotes());
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
|
||||
return entity.getAuthenticatorStatus();
|
||||
|
|
|
@ -26,10 +26,11 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
|
|||
private String authMethod;
|
||||
|
||||
private int timestamp;
|
||||
private ClientSessionModel.Action action;
|
||||
private String action;
|
||||
private List<String> roles;
|
||||
private List<String> protocolMappers;
|
||||
private Map<String, String> notes = new HashMap<String, String>();
|
||||
private Map<String, String> userSessionNotes = new HashMap<String, String>();
|
||||
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
|
||||
private String authUserId;
|
||||
|
||||
|
@ -81,11 +82,11 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
|
|||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public ClientSessionModel.Action getAction() {
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(ClientSessionModel.Action action) {
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
|
@ -113,6 +114,14 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
|
|||
this.notes = notes;
|
||||
}
|
||||
|
||||
public Map<String, String> getUserSessionNotes() {
|
||||
return userSessionNotes;
|
||||
}
|
||||
|
||||
public void setUserSessionNotes(Map<String, String> userSessionNotes) {
|
||||
this.userSessionNotes = userSessionNotes;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.jboss.resteasy.spi.HttpRequest;
|
|||
import org.jboss.resteasy.spi.HttpResponse;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.dom.saml.v2.SAML2Object;
|
||||
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
|
||||
|
@ -17,11 +18,14 @@ import org.keycloak.events.Errors;
|
|||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||
|
@ -30,6 +34,7 @@ import org.keycloak.saml.common.exceptions.ConfigurationException;
|
|||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.HttpAuthenticationManager;
|
||||
|
@ -57,6 +62,7 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.security.PublicKey;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Resource class for the oauth/openid connect token service
|
||||
|
@ -262,7 +268,7 @@ public class SamlService {
|
|||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||
clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
|
||||
clientSession.setRedirectUri(redirect);
|
||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||
clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
|
||||
clientSession.setNote(SamlProtocol.SAML_BINDING, bindingType);
|
||||
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
|
||||
|
@ -282,6 +288,10 @@ public class SamlService {
|
|||
}
|
||||
}
|
||||
|
||||
return newBrowserAuthentication(clientSession);
|
||||
}
|
||||
|
||||
private Response oldBrowserAuthentication(ClientSessionModel clientSession) {
|
||||
Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
|
||||
if (response != null) return response;
|
||||
|
||||
|
@ -311,6 +321,42 @@ public class SamlService {
|
|||
return forms.createLogin();
|
||||
}
|
||||
|
||||
private Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
|
||||
logger.debug("Automatically redirect to identity provider: " + providerId);
|
||||
return Response.temporaryRedirect(
|
||||
Urls.identityProviderAuthnRequest(uriInfo.getBaseUri(), providerId, realm.getName(), accessCode))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
protected Response newBrowserAuthentication(ClientSessionModel clientSession) {
|
||||
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
||||
for (IdentityProviderModel identityProvider : identityProviders) {
|
||||
if (identityProvider.isAuthenticateByDefault()) {
|
||||
return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode() );
|
||||
}
|
||||
}
|
||||
AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
|
||||
String flowId = flow.getId();
|
||||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||
processor.setClientSession(clientSession)
|
||||
.setFlowId(flowId)
|
||||
.setConnection(clientConnection)
|
||||
.setEventBuilder(event)
|
||||
.setProtector(authManager.getProtector())
|
||||
.setRealm(realm)
|
||||
.setSession(session)
|
||||
.setUriInfo(uriInfo)
|
||||
.setRequest(request);
|
||||
|
||||
try {
|
||||
return processor.authenticate();
|
||||
} catch (Exception e) {
|
||||
return processor.handleBrowserException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getBindingType(AuthnRequestType requestAbstractType) {
|
||||
URI requestedProtocolBinding = requestAbstractType.getProtocolBinding();
|
||||
|
||||
|
@ -365,7 +411,7 @@ public class SamlService {
|
|||
// remove client from logout requests
|
||||
for (ClientSessionModel clientSession : userSession.getClientSessions()) {
|
||||
if (clientSession.getClient().getId().equals(client.getId())) {
|
||||
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
|
||||
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
|
||||
}
|
||||
}
|
||||
logger.debug("browser Logout");
|
||||
|
@ -377,13 +423,13 @@ public class SamlService {
|
|||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
if (clientSession.getClient().getClientId().equals(client.getClientId())) {
|
||||
// remove requesting client from logout
|
||||
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
|
||||
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
|
||||
|
||||
// Remove also other clientSessions of this client as there could be more in this UserSession
|
||||
if (userSession != null) {
|
||||
for (ClientSessionModel clientSession2 : userSession.getClientSessions()) {
|
||||
if (clientSession2.getClient().getId().equals(client.getId())) {
|
||||
clientSession2.setAction(ClientSessionModel.Action.LOGGED_OUT);
|
||||
clientSession2.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -514,7 +560,6 @@ public class SamlService {
|
|||
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
|
||||
logger.debug("SAML GET");
|
||||
//String uri = uriInfo.getRequestUri().toString();
|
||||
return new RedirectBindingProtocol().execute(samlRequest, samlResponse, relayState);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,10 @@ package org.keycloak.authentication;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
|
@ -13,13 +16,15 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.BruteForceProtector;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -34,14 +39,17 @@ public class AuthenticationProcessor {
|
|||
protected UriInfo uriInfo;
|
||||
protected KeycloakSession session;
|
||||
protected BruteForceProtector protector;
|
||||
protected EventBuilder eventBuilder;
|
||||
protected EventBuilder event;
|
||||
protected HttpRequest request;
|
||||
protected String flowId;
|
||||
protected String action;
|
||||
protected boolean userSessionCreated;
|
||||
|
||||
|
||||
public static enum Status {
|
||||
SUCCESS,
|
||||
CHALLENGE,
|
||||
FORCE_CHALLENGE,
|
||||
FAILURE_CHALLENGE,
|
||||
FAILED,
|
||||
ATTEMPTED
|
||||
|
@ -78,6 +86,14 @@ public class AuthenticationProcessor {
|
|||
return session;
|
||||
}
|
||||
|
||||
public UserSessionModel getUserSession() {
|
||||
return userSession;
|
||||
}
|
||||
|
||||
public boolean isUserSessionCreated() {
|
||||
return userSessionCreated;
|
||||
}
|
||||
|
||||
public AuthenticationProcessor setRealm(RealmModel realm) {
|
||||
this.realm = realm;
|
||||
return this;
|
||||
|
@ -109,7 +125,7 @@ public class AuthenticationProcessor {
|
|||
}
|
||||
|
||||
public AuthenticationProcessor setEventBuilder(EventBuilder eventBuilder) {
|
||||
this.eventBuilder = eventBuilder;
|
||||
this.event = eventBuilder;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -123,28 +139,50 @@ public class AuthenticationProcessor {
|
|||
return this;
|
||||
}
|
||||
|
||||
public AuthenticationProcessor setAction(String action) {
|
||||
this.action = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
private class Result implements AuthenticatorContext {
|
||||
AuthenticatorModel model;
|
||||
AuthenticationExecutionModel execution;
|
||||
Authenticator authenticator;
|
||||
Status status;
|
||||
Response challenge;
|
||||
Error error;
|
||||
|
||||
private Result(AuthenticatorModel model, Authenticator authenticator) {
|
||||
private Result(AuthenticationExecutionModel execution, AuthenticatorModel model, Authenticator authenticator) {
|
||||
this.execution = execution;
|
||||
this.model = model;
|
||||
this.authenticator = authenticator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatorModel getModel() {
|
||||
public AuthenticationExecutionModel getExecution() {
|
||||
return execution;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExecution(AuthenticationExecutionModel execution) {
|
||||
this.execution = execution;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatorModel getAuthenticatorModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(AuthenticatorModel model) {
|
||||
public void setAuthenticatorModel(AuthenticatorModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAction() {
|
||||
return AuthenticationProcessor.this.action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator getAuthenticator() {
|
||||
return authenticator;
|
||||
|
@ -177,6 +215,12 @@ public class AuthenticationProcessor {
|
|||
this.status = Status.CHALLENGE;
|
||||
this.challenge = challenge;
|
||||
|
||||
}
|
||||
@Override
|
||||
public void forceChallenge(Response challenge) {
|
||||
this.status = Status.FORCE_CHALLENGE;
|
||||
this.challenge = challenge;
|
||||
|
||||
}
|
||||
@Override
|
||||
public void failureChallenge(Error error, Response challenge) {
|
||||
|
@ -251,6 +295,11 @@ public class AuthenticationProcessor {
|
|||
public BruteForceProtector getProtector() {
|
||||
return AuthenticationProcessor.this.protector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventBuilder getEvent() {
|
||||
return AuthenticationProcessor.this.event;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AuthException extends RuntimeException {
|
||||
|
@ -304,7 +353,44 @@ public class AuthenticationProcessor {
|
|||
return status == UserSessionModel.AuthenticatorStatus.SUCCESS;
|
||||
}
|
||||
|
||||
public Response handleBrowserException(Exception failure) {
|
||||
if (failure instanceof AuthException) {
|
||||
AuthException e = (AuthException)failure;
|
||||
logger.error("failed authentication: " + e.getError().toString(), e);
|
||||
if (e.getError() == AuthenticationProcessor.Error.INVALID_USER) {
|
||||
event.error(Errors.USER_NOT_FOUND);
|
||||
return ErrorPage.error(session, Messages.INVALID_USER);
|
||||
} else if (e.getError() == AuthenticationProcessor.Error.USER_DISABLED) {
|
||||
event.error(Errors.USER_DISABLED);
|
||||
return ErrorPage.error(session, Messages.ACCOUNT_DISABLED);
|
||||
} else if (e.getError() == AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED) {
|
||||
event.error(Errors.USER_TEMPORARILY_DISABLED);
|
||||
return ErrorPage.error(session, Messages.ACCOUNT_TEMPORARILY_DISABLED);
|
||||
|
||||
} else {
|
||||
event.error(Errors.INVALID_USER_CREDENTIALS);
|
||||
return ErrorPage.error(session, Messages.INVALID_USER);
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.error("failed authentication", failure);
|
||||
event.error(Errors.INVALID_USER_CREDENTIALS);
|
||||
return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public Response authenticate() throws AuthException {
|
||||
logger.debug("AUTHENTICATE");
|
||||
event.event(EventType.LOGIN);
|
||||
event.client(clientSession.getClient().getClientId())
|
||||
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
|
||||
.detail(Details.AUTH_METHOD, clientSession.getAuthMethod());
|
||||
String authType = clientSession.getNote(Details.AUTH_TYPE);
|
||||
if (authType != null) {
|
||||
event.detail(Details.AUTH_TYPE, authType);
|
||||
}
|
||||
UserModel authUser = clientSession.getAuthenticatedUser();
|
||||
validateUser(authUser);
|
||||
Response challenge = processFlow(flowId);
|
||||
|
@ -313,6 +399,40 @@ public class AuthenticationProcessor {
|
|||
throw new AuthException(Error.UNKNOWN_USER);
|
||||
}
|
||||
return authenticationComplete();
|
||||
}
|
||||
|
||||
public Response authenticateOnly() throws AuthException {
|
||||
event.event(EventType.LOGIN);
|
||||
event.client(clientSession.getClient().getClientId())
|
||||
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
|
||||
.detail(Details.AUTH_METHOD, clientSession.getAuthMethod());
|
||||
String authType = clientSession.getNote(Details.AUTH_TYPE);
|
||||
if (authType != null) {
|
||||
event.detail(Details.AUTH_TYPE, authType);
|
||||
}
|
||||
UserModel authUser = clientSession.getAuthenticatedUser();
|
||||
validateUser(authUser);
|
||||
Response challenge = processFlow(flowId);
|
||||
if (challenge != null) return challenge;
|
||||
|
||||
String username = clientSession.getAuthenticatedUser().getUsername();
|
||||
if (userSession == null) { // if no authenticator attached a usersession
|
||||
userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), "form", false, null, null);
|
||||
userSession.setState(UserSessionModel.State.LOGGING_IN);
|
||||
userSessionCreated = true;
|
||||
}
|
||||
TokenManager.attachClientSession(userSession, clientSession);
|
||||
event.user(userSession.getUser())
|
||||
.detail(Details.USERNAME, username)
|
||||
.session(userSession);
|
||||
|
||||
return AuthenticationManager.actionRequired(session, userSession, clientSession, connection, request, uriInfo, event);
|
||||
}
|
||||
|
||||
public Response finishAuthentication() {
|
||||
event.success();
|
||||
RealmModel realm = clientSession.getRealm();
|
||||
return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection);
|
||||
|
||||
}
|
||||
|
||||
|
@ -325,9 +445,11 @@ public class AuthenticationProcessor {
|
|||
List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(flowId);
|
||||
if (executions == null) return null;
|
||||
Response alternativeChallenge = null;
|
||||
AuthenticationExecutionModel challengedAlternativeExecution = null;
|
||||
boolean alternativeSuccessful = false;
|
||||
for (AuthenticationExecutionModel model : executions) {
|
||||
if (isProcessed(model)) {
|
||||
logger.debug("execution is processed");
|
||||
if (!alternativeSuccessful && model.isAlternative() && isSuccessful(model)) alternativeSuccessful = true;
|
||||
continue;
|
||||
}
|
||||
|
@ -351,49 +473,82 @@ public class AuthenticationProcessor {
|
|||
AuthenticatorModel authenticatorModel = realm.getAuthenticatorById(model.getAuthenticator());
|
||||
AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, authenticatorModel.getProviderId());
|
||||
Authenticator authenticator = factory.create(authenticatorModel);
|
||||
logger.debugv("authenticator: {0}", authenticatorModel.getProviderId());
|
||||
UserModel authUser = clientSession.getAuthenticatedUser();
|
||||
|
||||
if (authenticator.requiresUser() && authUser == null){
|
||||
if (alternativeChallenge != null) return alternativeChallenge;
|
||||
throw new AuthException(Error.UNKNOWN_USER);
|
||||
if (alternativeChallenge != null) {
|
||||
clientSession.setAuthenticatorStatus(challengedAlternativeExecution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
|
||||
return alternativeChallenge;
|
||||
}
|
||||
throw new AuthException("authenticator: " + authenticatorModel.getProviderId(), Error.UNKNOWN_USER);
|
||||
}
|
||||
|
||||
if (authenticator.requiresUser() && authUser != null && !authenticator.configuredFor(authUser)) {
|
||||
if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
|
||||
if (model.isUserSetupAllowed()) {
|
||||
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED);
|
||||
authUser.addRequiredAction(authenticator.getRequiredAction());
|
||||
|
||||
} else {
|
||||
throw new AuthException(Error.CREDENTIAL_SETUP_REQUIRED);
|
||||
boolean configuredFor = false;
|
||||
if (authenticator.requiresUser() && authUser != null) {
|
||||
configuredFor = authenticator.configuredFor(session, realm, authUser);
|
||||
if (!configuredFor) {
|
||||
if (model.isRequired()) {
|
||||
if (model.isUserSetupAllowed()) {
|
||||
logger.debugv("authenticator SETUP_REQUIRED: {0}", authenticatorModel.getProviderId());
|
||||
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED);
|
||||
String requiredAction = authenticator.getRequiredAction();
|
||||
if (!authUser.getRequiredActions().contains(requiredAction)) {
|
||||
authUser.addRequiredAction(requiredAction);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
throw new AuthException(Error.CREDENTIAL_SETUP_REQUIRED);
|
||||
}
|
||||
} else if (model.isOptional()) {
|
||||
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
context = new Result(authenticatorModel, authenticator);
|
||||
context = new Result(model, authenticatorModel, authenticator);
|
||||
authenticator.authenticate(context);
|
||||
Status result = context.getStatus();
|
||||
if (result == Status.SUCCESS){
|
||||
logger.debugv("authenticator SUCCESS: {0}", authenticatorModel.getProviderId());
|
||||
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SUCCESS);
|
||||
if (model.isAlternative()) alternativeSuccessful = true;
|
||||
continue;
|
||||
} else if (result == Status.FAILED) {
|
||||
logger.debugv("authenticator FAILED: {0}", authenticatorModel.getProviderId());
|
||||
logUserFailure();
|
||||
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.FAILED);
|
||||
if (context.challenge != null) return context.challenge;
|
||||
throw new AuthException(context.error);
|
||||
} else if (result == Status.FORCE_CHALLENGE) {
|
||||
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
|
||||
return context.challenge;
|
||||
} else if (result == Status.CHALLENGE) {
|
||||
if (model.isRequired()) return context.challenge;
|
||||
else if (model.isAlternative()) alternativeChallenge = context.challenge;
|
||||
else clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED);
|
||||
logger.debugv("authenticator CHALLENGE: {0}", authenticatorModel.getProviderId());
|
||||
if (model.isRequired() || (model.isOptional() && configuredFor)) {
|
||||
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
|
||||
return context.challenge;
|
||||
}
|
||||
else if (model.isAlternative()) {
|
||||
alternativeChallenge = context.challenge;
|
||||
challengedAlternativeExecution = model;
|
||||
} else {
|
||||
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED);
|
||||
}
|
||||
continue;
|
||||
} else if (result == Status.FAILURE_CHALLENGE) {
|
||||
logger.debugv("authenticator FAILURE_CHALLENGE: {0}", authenticatorModel.getProviderId());
|
||||
logUserFailure();
|
||||
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
|
||||
return context.challenge;
|
||||
} else if (result == Status.ATTEMPTED) {
|
||||
if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) throw new AuthException(Error.INVALID_CREDENTIALS);
|
||||
logger.debugv("authenticator ATTEMPTED: {0}", authenticatorModel.getProviderId());
|
||||
if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
|
||||
throw new AuthException(Error.INVALID_CREDENTIALS);
|
||||
}
|
||||
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.ATTEMPTED);
|
||||
continue;
|
||||
} else {
|
||||
logger.debugv("authenticator INTERNAL_ERROR: {0}", authenticatorModel.getProviderId());
|
||||
logger.error("Unknown result status");
|
||||
throw new AuthException(Error.INTERNAL_ERROR);
|
||||
}
|
||||
|
@ -415,17 +570,27 @@ public class AuthenticationProcessor {
|
|||
}
|
||||
|
||||
protected Response authenticationComplete() {
|
||||
String username = clientSession.getAuthenticatedUser().getUsername();
|
||||
String rememberMe = clientSession.getNote(Details.REMEMBER_ME);
|
||||
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("true");
|
||||
if (userSession == null) { // if no authenticator attached a usersession
|
||||
userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), clientSession.getAuthenticatedUser().getUsername(), connection.getRemoteAddr(), "form", false, null, null);
|
||||
userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), clientSession.getAuthMethod(), remember, null, null);
|
||||
userSession.setState(UserSessionModel.State.LOGGING_IN);
|
||||
}
|
||||
if (remember) {
|
||||
event.detail(Details.REMEMBER_ME, "true");
|
||||
}
|
||||
TokenManager.attachClientSession(userSession, clientSession);
|
||||
event.user(userSession.getUser())
|
||||
.detail(Details.USERNAME, username)
|
||||
.session(userSession);
|
||||
|
||||
return processRequiredActions();
|
||||
|
||||
}
|
||||
|
||||
public Response processRequiredActions() {
|
||||
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, eventBuilder);
|
||||
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, event);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
|
@ -10,7 +12,7 @@ import org.keycloak.provider.Provider;
|
|||
public interface Authenticator extends Provider {
|
||||
boolean requiresUser();
|
||||
void authenticate(AuthenticatorContext context);
|
||||
boolean configuredFor(UserModel user);
|
||||
boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user);
|
||||
String getRequiredAction();
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ package org.keycloak.authentication;
|
|||
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -19,9 +21,17 @@ import javax.ws.rs.core.UriInfo;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface AuthenticatorContext {
|
||||
AuthenticatorModel getModel();
|
||||
EventBuilder getEvent();
|
||||
|
||||
void setModel(AuthenticatorModel model);
|
||||
AuthenticationExecutionModel getExecution();
|
||||
|
||||
void setExecution(AuthenticationExecutionModel execution);
|
||||
|
||||
AuthenticatorModel getAuthenticatorModel();
|
||||
|
||||
void setAuthenticatorModel(AuthenticatorModel model);
|
||||
|
||||
String getAction();
|
||||
|
||||
Authenticator getAuthenticator();
|
||||
|
||||
|
@ -51,6 +61,9 @@ public interface AuthenticatorContext {
|
|||
void failure(AuthenticationProcessor.Error error);
|
||||
void failure(AuthenticationProcessor.Error error, Response response);
|
||||
void challenge(Response challenge);
|
||||
|
||||
void forceChallenge(Response challenge);
|
||||
|
||||
void failureChallenge(AuthenticationProcessor.Error error, Response challenge);
|
||||
void attempted();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.provider.ConfiguredProvider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
|
@ -13,4 +16,21 @@ public interface AuthenticatorFactory extends ProviderFactory<Authenticator>, Co
|
|||
String getDisplayCategory();
|
||||
String getDisplayType();
|
||||
|
||||
/**
|
||||
* General authenticator type, i.e. totp, password, cert
|
||||
*
|
||||
* @return null if not a referencable type
|
||||
*/
|
||||
String getReferenceType();
|
||||
|
||||
boolean isConfigurable();
|
||||
|
||||
/**
|
||||
* What requirement settings are allowed. For example, KERBEROS can only be required because of the way its challenges
|
||||
* work.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
AuthenticationExecutionModel.Requirement[] getRequirementChoices();
|
||||
|
||||
}
|
||||
|
|
67
services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java
Executable file
67
services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java
Executable file
|
@ -0,0 +1,67 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AuthenticatorUtil {
|
||||
|
||||
public static List<AuthenticationExecutionModel> getEnabledExecutionsRecursively(RealmModel realm, String flowId) {
|
||||
List<AuthenticationExecutionModel> executions = new LinkedList<>();
|
||||
recurseExecutions(realm, flowId, executions);
|
||||
return executions;
|
||||
|
||||
}
|
||||
|
||||
public static void recurseExecutions(RealmModel realm, String flowId, List<AuthenticationExecutionModel> executions) {
|
||||
for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
|
||||
executions.add(model);
|
||||
if (model.isAutheticatorFlow() && model.isEnabled()) {
|
||||
recurseExecutions(realm, model.getAuthenticator(), executions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static AuthenticationExecutionModel findExecutionByAuthenticator(RealmModel realm, String flowId, String authProviderId) {
|
||||
for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
|
||||
if (model.isAutheticatorFlow()) {
|
||||
AuthenticationExecutionModel recurse = findExecutionByAuthenticator(realm, model.getAuthenticator(), authProviderId);
|
||||
if (recurse != null) return recurse;
|
||||
|
||||
}
|
||||
AuthenticatorModel authenticator = realm.getAuthenticatorById(model.getAuthenticator());
|
||||
if (authenticator.getProviderId().equals(authProviderId)) {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isEnabled(RealmModel realm, String flowId, String authProviderId) {
|
||||
AuthenticationExecutionModel execution = findExecutionByAuthenticator(realm, flowId, authProviderId);
|
||||
if (execution == null) {
|
||||
return false;
|
||||
}
|
||||
return execution.isEnabled();
|
||||
}
|
||||
public static boolean isRequired(RealmModel realm, String flowId, String authProviderId) {
|
||||
AuthenticationExecutionModel execution = findExecutionByAuthenticator(realm, flowId, authProviderId);
|
||||
if (execution == null) {
|
||||
return false;
|
||||
}
|
||||
return execution.isRequired();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.services.managers.BruteForceProtector;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface RequiredActionContext {
|
||||
EventBuilder getEvent();
|
||||
UserModel getUser();
|
||||
RealmModel getRealm();
|
||||
ClientSessionModel getClientSession();
|
||||
UserSessionModel getUserSession();
|
||||
ClientConnection getConnection();
|
||||
UriInfo getUriInfo();
|
||||
KeycloakSession getSession();
|
||||
HttpRequest getHttpRequest();
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface RequiredActionFactory extends ProviderFactory<RequiredActionProvider> {
|
||||
String getDisplayText();
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface RequiredActionProvider extends Provider {
|
||||
void evaluateTriggers(RequiredActionContext context);
|
||||
Response invokeRequiredAction(RequiredActionContext context);
|
||||
Object jaxrsService(RequiredActionContext context);
|
||||
}
|
32
services/src/main/java/org/keycloak/authentication/RequiredActionSpi.java
Executable file
32
services/src/main/java/org/keycloak/authentication/RequiredActionSpi.java
Executable file
|
@ -0,0 +1,32 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RequiredActionSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "required-action";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return RequiredActionProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return RequiredActionFactory.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package org.keycloak.authentication.actions;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.Version;
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
||||
import org.keycloak.freemarker.FreeMarkerException;
|
||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||
import org.keycloak.freemarker.Theme;
|
||||
import org.keycloak.freemarker.ThemeProvider;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class TermsAndConditions implements RequiredActionProvider, RequiredActionFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "terms_and_conditions";
|
||||
|
||||
public static class Resource {
|
||||
|
||||
public Resource(RequiredActionContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
protected RequiredActionContext context;
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response agree(final MultivaluedMap<String, String> formData) throws URISyntaxException, IOException, FreeMarkerException {
|
||||
if (formData.containsKey("cancel")) {
|
||||
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
|
||||
protocol.setRealm(context.getRealm())
|
||||
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
|
||||
.setUriInfo(context.getUriInfo());
|
||||
context.getEvent().error(Errors.REJECTED_BY_USER);
|
||||
return protocol.consentDenied(context.getClientSession());
|
||||
}
|
||||
context.getUser().removeRequiredAction(PROVIDER_ID);
|
||||
return AuthenticationManager.nextActionAfterAuthentication(context.getSession(), context.getUserSession(), context.getClientSession(), context.getConnection(), context.getHttpRequest(), context.getUriInfo(), context.getEvent());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequiredActionProvider create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluateTriggers(RequiredActionContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response invokeRequiredAction(RequiredActionContext context) {
|
||||
return context.getSession().getProvider(LoginFormsProvider.class)
|
||||
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
|
||||
.setUser(context.getUser())
|
||||
.createForm("terms.ftl", new HashMap<String, Object>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jaxrsService(RequiredActionContext context) {
|
||||
return new Resource(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return "Terms and Conditions";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package org.keycloak.authentication.actions;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UpdatePassword implements RequiredActionProvider, RequiredActionFactory {
|
||||
protected static Logger logger = Logger.getLogger(UpdatePassword.class);
|
||||
@Override
|
||||
public void evaluateTriggers(RequiredActionContext context) {
|
||||
int daysToExpirePassword = context.getRealm().getPasswordPolicy().getDaysToExpirePassword();
|
||||
if(daysToExpirePassword != -1) {
|
||||
for (UserCredentialValueModel entity : context.getUser().getCredentialsDirectly()) {
|
||||
if (entity.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||
|
||||
if(entity.getCreatedDate() == null) {
|
||||
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
logger.debug("User is required to update password");
|
||||
} else {
|
||||
long timeElapsed = Time.toMillis(Time.currentTime()) - entity.getCreatedDate();
|
||||
long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword);
|
||||
|
||||
if(timeElapsed > timeToExpire) {
|
||||
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
logger.debug("User is required to update password");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response invokeRequiredAction(RequiredActionContext context) {
|
||||
ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession());
|
||||
accessCode.setAction(ClientSessionModel.Action.UPDATE_PASSWORD.name());
|
||||
|
||||
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode())
|
||||
.setUser(context.getUser());
|
||||
return loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jaxrsService(RequiredActionContext context) {
|
||||
// this is handled by LoginActionsService at the moment
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequiredActionProvider create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return "Update Password";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return UserModel.RequiredAction.UPDATE_PASSWORD.name();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package org.keycloak.authentication.actions;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RequiredCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UpdateProfile implements RequiredActionProvider, RequiredActionFactory {
|
||||
protected static Logger logger = Logger.getLogger(UpdateProfile.class);
|
||||
@Override
|
||||
public void evaluateTriggers(RequiredActionContext context) {
|
||||
if (context.getRealm().isVerifyEmail() && !context.getUser().isEmailVerified()) {
|
||||
context.getUser().addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
|
||||
logger.debug("User is required to verify email");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response invokeRequiredAction(RequiredActionContext context) {
|
||||
ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession());
|
||||
accessCode.setAction(ClientSessionModel.Action.UPDATE_PROFILE.name());
|
||||
|
||||
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode())
|
||||
.setUser(context.getUser());
|
||||
return loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jaxrsService(RequiredActionContext context) {
|
||||
// this is handled by LoginActionsService at the moment
|
||||
// todo should be refactored to contain it here
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequiredActionProvider create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return "Update Profile";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return UserModel.RequiredAction.UPDATE_PROFILE.name();
|
||||
}
|
||||
}
|
90
services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java
Executable file
90
services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java
Executable file
|
@ -0,0 +1,90 @@
|
|||
package org.keycloak.authentication.actions;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RequiredCredentialModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory {
|
||||
protected static Logger logger = Logger.getLogger(UpdateTotp.class);
|
||||
@Override
|
||||
public void evaluateTriggers(RequiredActionContext context) {
|
||||
// I don't think we need this check here. AuthenticationProcessor should be setting the required action
|
||||
// if OTP changes from required from optional or disabled
|
||||
for (RequiredCredentialModel c : context.getRealm().getRequiredCredentials()) {
|
||||
if (c.getType().equals(CredentialRepresentation.TOTP) && !context.getUser().isTotp()) {
|
||||
context.getUser().addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
|
||||
logger.debug("User is required to configure totp");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response invokeRequiredAction(RequiredActionContext context) {
|
||||
ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession());
|
||||
accessCode.setAction(ClientSessionModel.Action.CONFIGURE_TOTP.name());
|
||||
|
||||
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode())
|
||||
.setUser(context.getUser());
|
||||
return loginFormsProvider.createResponse(UserModel.RequiredAction.CONFIGURE_TOTP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jaxrsService(RequiredActionContext context) {
|
||||
// this is handled by LoginActionsService at the moment
|
||||
// todo should be refactored to contain it here
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequiredActionProvider create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return "Configure Totp";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return UserModel.RequiredAction.CONFIGURE_TOTP.name();
|
||||
}
|
||||
|
||||
}
|
110
services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java
Executable file
110
services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java
Executable file
|
@ -0,0 +1,110 @@
|
|||
package org.keycloak.authentication.actions;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.resources.LoginActionsService;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class VerifyEmail implements RequiredActionProvider, RequiredActionFactory {
|
||||
protected static Logger logger = Logger.getLogger(VerifyEmail.class);
|
||||
@Override
|
||||
public void evaluateTriggers(RequiredActionContext context) {
|
||||
int daysToExpirePassword = context.getRealm().getPasswordPolicy().getDaysToExpirePassword();
|
||||
if(daysToExpirePassword != -1) {
|
||||
for (UserCredentialValueModel entity : context.getUser().getCredentialsDirectly()) {
|
||||
if (entity.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||
|
||||
if(entity.getCreatedDate() == null) {
|
||||
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
logger.debug("User is required to update password");
|
||||
} else {
|
||||
long timeElapsed = Time.toMillis(Time.currentTime()) - entity.getCreatedDate();
|
||||
long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword);
|
||||
|
||||
if(timeElapsed > timeToExpire) {
|
||||
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
logger.debug("User is required to update password");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response invokeRequiredAction(RequiredActionContext context) {
|
||||
if (Validation.isBlank(context.getUser().getEmail())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession());
|
||||
accessCode.setAction(ClientSessionModel.Action.VERIFY_EMAIL.name());
|
||||
context.getEvent().clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, context.getUser().getEmail()).success();
|
||||
LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getUserSession().getId());
|
||||
|
||||
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode())
|
||||
.setUser(context.getUser());
|
||||
return loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jaxrsService(RequiredActionContext context) {
|
||||
// this is handled by LoginActionsService at the moment
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequiredActionProvider create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return "Verify Email";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return UserModel.RequiredAction.VERIFY_EMAIL.name();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package org.keycloak.authentication.authenticators;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.AuthenticatorContext;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.LoginActionsService;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AbstractFormAuthenticator {
|
||||
|
||||
public static final String LOGIN_FORM_ACTION = "login_form";
|
||||
public static final String REGISTRATION_FORM_ACTION = "registration_form";
|
||||
public static final String ACTION = "action";
|
||||
|
||||
protected boolean isAction(AuthenticatorContext context, String action) {
|
||||
return action.equals(context.getAction());
|
||||
}
|
||||
|
||||
protected LoginFormsProvider loginForm(AuthenticatorContext context) {
|
||||
ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
|
||||
code.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||
URI action = getActionUrl(context, code, LOGIN_FORM_ACTION);
|
||||
return context.getSession().getProvider(LoginFormsProvider.class)
|
||||
.setUser(context.getUser())
|
||||
.setActionUri(action)
|
||||
.setClientSessionCode(code.getCode());
|
||||
}
|
||||
|
||||
public static URI getActionUrl(AuthenticatorContext context, ClientSessionCode code, String action) {
|
||||
return LoginActionsService.authenticationFormProcessor(context.getUriInfo())
|
||||
.queryParam(OAuth2Constants.CODE, code.getCode())
|
||||
.queryParam(ACTION, action)
|
||||
.build(context.getRealm().getName());
|
||||
}
|
||||
|
||||
protected Response invalidUser(AuthenticatorContext context) {
|
||||
return loginForm(context)
|
||||
.setError(Messages.INVALID_USER)
|
||||
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
|
||||
.createLogin();
|
||||
}
|
||||
|
||||
protected Response disabledUser(AuthenticatorContext context) {
|
||||
return loginForm(context)
|
||||
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
|
||||
.setError(Messages.ACCOUNT_DISABLED).createLogin();
|
||||
}
|
||||
|
||||
protected Response temporarilyDisabledUser(AuthenticatorContext context) {
|
||||
return loginForm(context)
|
||||
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
|
||||
.setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).createLogin();
|
||||
}
|
||||
|
||||
protected Response invalidCredentials(AuthenticatorContext context) {
|
||||
return loginForm(context)
|
||||
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
|
||||
.setError(Messages.INVALID_USER).createLogin();
|
||||
}
|
||||
|
||||
public boolean invalidUser(AuthenticatorContext context, UserModel user) {
|
||||
if (user == null) {
|
||||
context.getEvent().error(Errors.USER_NOT_FOUND);
|
||||
Response challengeResponse = invalidUser(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||
return true;
|
||||
}
|
||||
if (!user.isEnabled()) {
|
||||
context.getEvent().user(user);
|
||||
context.getEvent().error(Errors.USER_DISABLED);
|
||||
Response challengeResponse = disabledUser(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.USER_DISABLED, challengeResponse);
|
||||
return true;
|
||||
}
|
||||
if (context.getRealm().isBruteForceProtected()) {
|
||||
if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) {
|
||||
context.getEvent().user(user);
|
||||
context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED);
|
||||
Response challengeResponse = temporarilyDisabledUser(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED, challengeResponse);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package org.keycloak.authentication.authenticators;
|
||||
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AuthenticationFlow {
|
||||
|
||||
/**
|
||||
* Hardcoded models just to test this stuff. It is temporary
|
||||
*/
|
||||
static List<AuthenticationExecutionModel> hardcoded = new ArrayList<>();
|
||||
|
||||
/*
|
||||
static {
|
||||
AuthenticationExecutionModel model = new AuthenticationExecutionModel();
|
||||
model.setId("1");
|
||||
model.setAlias("cookie");
|
||||
model.setMasterAuthenticator(true);
|
||||
model.setProviderId(CookieAuthenticatorFactory.PROVIDER_ID);
|
||||
model.setPriority(0);
|
||||
model.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
|
||||
model.setUserSetupAllowed(false);
|
||||
hardcoded.add(model);
|
||||
model = new AuthenticatorModel();
|
||||
model.setId("2");
|
||||
model.setAlias("user form");
|
||||
model.setMasterAuthenticator(false);
|
||||
model.setProviderId(LoginFormUsernameAuthenticatorFactory.PROVIDER_ID);
|
||||
model.setPriority(1);
|
||||
model.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
model.setUserSetupAllowed(false);
|
||||
hardcoded.add(model);
|
||||
model = new AuthenticatorModel();
|
||||
model.setId("3");
|
||||
model.setAlias("password form");
|
||||
model.setMasterAuthenticator(false);
|
||||
model.setProviderId(LoginFormUsernameAuthenticatorFactory.PROVIDER_ID);
|
||||
model.setPriority(2);
|
||||
model.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
model.setUserSetupAllowed(false);
|
||||
hardcoded.add(model);
|
||||
model = new AuthenticatorModel();
|
||||
model.setId("4");
|
||||
model.setAlias("otp form");
|
||||
model.setMasterAuthenticator(false);
|
||||
model.setProviderId(OTPFormAuthenticatorFactory.PROVIDER_ID);
|
||||
model.setPriority(3);
|
||||
model.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL);
|
||||
model.setUserSetupAllowed(false);
|
||||
hardcoded.add(model);
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -2,6 +2,8 @@ package org.keycloak.authentication.authenticators;
|
|||
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
|
||||
|
@ -31,7 +33,7 @@ public class CookieAuthenticator implements Authenticator {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(UserModel user) {
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
|
|||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -47,6 +49,23 @@ public class CookieAuthenticatorFactory implements AuthenticatorFactory {
|
|||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceType() {
|
||||
return "cookie";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {AuthenticationExecutionModel.Requirement.ALTERNATIVE, AuthenticationExecutionModel.Requirement.DISABLED};
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "Complete Authenticator";
|
||||
|
|
|
@ -3,6 +3,8 @@ package org.keycloak.authentication.authenticators;
|
|||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.AuthenticatorContext;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
@ -25,7 +27,7 @@ public class LoginFormOTPAuthenticator extends LoginFormUsernameAuthenticator {
|
|||
|
||||
@Override
|
||||
public void authenticate(AuthenticatorContext context) {
|
||||
if (!isActionUrl(context)) {
|
||||
if (!isAction(context, LOGIN_FORM_ACTION)) {
|
||||
context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
@ -61,8 +63,8 @@ public class LoginFormOTPAuthenticator extends LoginFormUsernameAuthenticator {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(UserModel user) {
|
||||
return user.configuredForCredentialType(UserCredentialModel.TOTP);
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return session.users().configuredForCredentialType(UserCredentialModel.TOTP, realm, user) && user.isTotp();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
|
|||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -38,6 +40,27 @@ public class LoginFormOTPAuthenticatorFactory implements AuthenticatorFactory {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceType() {
|
||||
return UserCredentialModel.TOTP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED,
|
||||
AuthenticationExecutionModel.Requirement.OPTIONAL,
|
||||
AuthenticationExecutionModel.Requirement.DISABLED};
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
|
|
|
@ -2,11 +2,13 @@ package org.keycloak.authentication.authenticators;
|
|||
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.AuthenticatorContext;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
@ -25,31 +27,32 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
|
|||
|
||||
@Override
|
||||
public void authenticate(AuthenticatorContext context) {
|
||||
if (!isActionUrl(context)) {
|
||||
if (!isAction(context, LOGIN_FORM_ACTION) && !isAction(context, REGISTRATION_FORM_ACTION)) {
|
||||
context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR);
|
||||
return;
|
||||
}
|
||||
validatePassword(context);
|
||||
}
|
||||
|
||||
protected Response badPassword(AuthenticatorContext context) {
|
||||
return loginForm(context).setError(Messages.INVALID_USER).createLogin();
|
||||
}
|
||||
|
||||
|
||||
public void validatePassword(AuthenticatorContext context) {
|
||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
|
||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
|
||||
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
||||
if (password == null) {
|
||||
Response challengeResponse = badPassword(context);
|
||||
if (context.getUser() != null) {
|
||||
context.getEvent().user(context.getUser());
|
||||
}
|
||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||
Response challengeResponse = invalidCredentials(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
credentials.add(UserCredentialModel.password(password));
|
||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||
if (!valid) {
|
||||
Response challengeResponse = badPassword(context);
|
||||
context.getEvent().user(context.getUser());
|
||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||
Response challengeResponse = invalidCredentials(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
|
@ -62,8 +65,8 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(UserModel user) {
|
||||
return user.configuredForCredentialType(UserCredentialModel.PASSWORD);
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return session.users().configuredForCredentialType(UserCredentialModel.PASSWORD, realm, user);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
|
|||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -48,6 +50,24 @@ public class LoginFormPasswordAuthenticatorFactory implements AuthenticatorFacto
|
|||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceType() {
|
||||
return UserCredentialModel.PASSWORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return true;
|
||||
}
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED,
|
||||
AuthenticationExecutionModel.Requirement.DISABLED};
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "Credential Validation";
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
package org.keycloak.authentication.authenticators;
|
||||
|
||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorContext;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.LoginActionsService;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class LoginFormUsernameAuthenticator implements Authenticator {
|
||||
public class LoginFormUsernameAuthenticator extends AbstractFormAuthenticator implements Authenticator {
|
||||
public static final String FORM_USERNAME = "FORM_USERNAME";
|
||||
protected AuthenticatorModel model;
|
||||
|
||||
public LoginFormUsernameAuthenticator(AuthenticatorModel model) {
|
||||
|
@ -33,29 +33,41 @@ public class LoginFormUsernameAuthenticator implements Authenticator {
|
|||
|
||||
@Override
|
||||
public void authenticate(AuthenticatorContext context) {
|
||||
if (!isActionUrl(context)) {
|
||||
if (isAction(context, REGISTRATION_FORM_ACTION) && context.getUser() != null) {
|
||||
context.success();
|
||||
return;
|
||||
}
|
||||
if (!isAction(context, LOGIN_FORM_ACTION)) {
|
||||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
|
||||
String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
|
||||
if (loginHint == null) {
|
||||
loginHint = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders());
|
||||
|
||||
String rememberMeUsername = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders());
|
||||
|
||||
if (loginHint != null || rememberMeUsername != null) {
|
||||
if (loginHint != null) {
|
||||
formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
|
||||
} else {
|
||||
formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
|
||||
formData.add("rememberMe", "on");
|
||||
}
|
||||
}
|
||||
if (loginHint != null) formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
|
||||
Response challengeResponse = challenge(context, formData);
|
||||
context.challenge(challengeResponse);
|
||||
return;
|
||||
}
|
||||
validateUser(context);
|
||||
}
|
||||
|
||||
protected boolean isActionUrl(AuthenticatorContext context) {
|
||||
URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName());
|
||||
String current = context.getUriInfo().getAbsolutePath().getPath();
|
||||
String expectedPath = expected.getPath();
|
||||
return expectedPath.equals(current);
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
if (formData.containsKey("cancel")) {
|
||||
context.getEvent().error(Errors.REJECTED_BY_USER);
|
||||
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
|
||||
protocol.setRealm(context.getRealm())
|
||||
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
|
||||
.setUriInfo(context.getUriInfo());
|
||||
Response response = protocol.cancelLogin(context.getClientSession());
|
||||
context.challenge(response);
|
||||
return;
|
||||
}
|
||||
|
||||
validateUser(context, formData);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -71,71 +83,30 @@ public class LoginFormUsernameAuthenticator implements Authenticator {
|
|||
return forms.createLogin();
|
||||
}
|
||||
|
||||
protected LoginFormsProvider loginForm(AuthenticatorContext context) {
|
||||
ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
|
||||
code.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||
URI action = LoginActionsService.authenticationFormProcessor(context.getUriInfo())
|
||||
.queryParam(OAuth2Constants.CODE, code.getCode())
|
||||
.build(context.getRealm().getName());
|
||||
return context.getSession().getProvider(LoginFormsProvider.class)
|
||||
.setActionUri(action)
|
||||
.setClientSessionCode(code.getCode());
|
||||
}
|
||||
|
||||
protected Response invalidUser(AuthenticatorContext context) {
|
||||
return loginForm(context).setError(Messages.INVALID_USER).createLogin();
|
||||
}
|
||||
|
||||
protected Response disabledUser(AuthenticatorContext context) {
|
||||
return loginForm(context).setError(Messages.ACCOUNT_DISABLED).createLogin();
|
||||
}
|
||||
|
||||
protected Response temporarilyDisabledUser(AuthenticatorContext context) {
|
||||
return loginForm(context).setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).createLogin();
|
||||
}
|
||||
|
||||
public void validateUser(AuthenticatorContext context) {
|
||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
|
||||
public void validateUser(AuthenticatorContext context, MultivaluedMap<String, String> inputData) {
|
||||
String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME);
|
||||
if (username == null) {
|
||||
context.getEvent().error(Errors.USER_NOT_FOUND);
|
||||
Response challengeResponse = invalidUser(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||
return;
|
||||
}
|
||||
context.getEvent().detail(Details.USERNAME, username);
|
||||
context.getClientSession().setNote(FORM_USERNAME, username);
|
||||
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
|
||||
if (invalidUser(context, user)) return;
|
||||
String rememberMe = inputData.getFirst("rememberMe");
|
||||
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
|
||||
if (remember) {
|
||||
context.getClientSession().setNote(Details.REMEMBER_ME, "true");
|
||||
context.getEvent().detail(Details.REMEMBER_ME, "true");
|
||||
}
|
||||
context.setUser(user);
|
||||
context.success();
|
||||
}
|
||||
|
||||
public boolean invalidUser(AuthenticatorContext context, UserModel user) {
|
||||
if (user == null) {
|
||||
Response challengeResponse = invalidUser(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||
return true;
|
||||
}
|
||||
if (!user.isEnabled()) {
|
||||
Response challengeResponse = disabledUser(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.USER_DISABLED, challengeResponse);
|
||||
return true;
|
||||
}
|
||||
if (context.getRealm().isBruteForceProtected()) {
|
||||
if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) {
|
||||
Response challengeResponse = temporarilyDisabledUser(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED, challengeResponse);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Response challenge(AuthenticatorContext context) {
|
||||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
|
||||
return challenge(context, formData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(UserModel user) {
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
|
|||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -48,6 +50,26 @@ public class LoginFormUsernameAuthenticatorFactory implements AuthenticatorFacto
|
|||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
|
||||
AuthenticationExecutionModel.Requirement.DISABLED};
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "User Validation";
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
package org.keycloak.authentication.authenticators;
|
||||
|
||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorContext;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.resources.LoginActionsService;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
@ -24,7 +24,8 @@ import java.util.List;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OTPFormAuthenticator implements Authenticator {
|
||||
public class OTPFormAuthenticator extends AbstractFormAuthenticator implements Authenticator {
|
||||
public static final String TOTP_FORM_ACTION = "totp";
|
||||
protected AuthenticatorModel model;
|
||||
|
||||
public OTPFormAuthenticator(AuthenticatorModel model) {
|
||||
|
@ -33,9 +34,8 @@ public class OTPFormAuthenticator implements Authenticator {
|
|||
|
||||
@Override
|
||||
public void authenticate(AuthenticatorContext context) {
|
||||
URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName());
|
||||
if (!expected.getPath().equals(context.getUriInfo().getPath())) {
|
||||
Response challengeResponse = challenge(context);
|
||||
if (!isAction(context, TOTP_FORM_ACTION)) {
|
||||
Response challengeResponse = challenge(context, null);
|
||||
context.challenge(challengeResponse);
|
||||
return;
|
||||
}
|
||||
|
@ -43,47 +43,45 @@ public class OTPFormAuthenticator implements Authenticator {
|
|||
}
|
||||
|
||||
public void validateOTP(AuthenticatorContext context) {
|
||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
|
||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
|
||||
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||
String password = inputData.getFirst(CredentialRepresentation.TOTP);
|
||||
if (password == null) {
|
||||
Response challengeResponse = challenge(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
Response challengeResponse = challenge(context, null);
|
||||
context.challenge(challengeResponse);
|
||||
return;
|
||||
}
|
||||
credentials.add(UserCredentialModel.totp(password));
|
||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||
if (!valid) {
|
||||
Response challengeResponse = challenge(context);
|
||||
context.getEvent().user(context.getUser())
|
||||
.error(Errors.INVALID_USER_CREDENTIALS);
|
||||
Response challengeResponse = challenge(context, Messages.INVALID_TOTP);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
context.success();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Response challenge(AuthenticatorContext context, MultivaluedMap<String, String> formData) {
|
||||
protected Response challenge(AuthenticatorContext context, String error) {
|
||||
ClientSessionCode clientSessionCode = new ClientSessionCode(context.getRealm(), context.getClientSession());
|
||||
URI action = AbstractFormAuthenticator.getActionUrl(context, clientSessionCode, TOTP_FORM_ACTION);
|
||||
LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class)
|
||||
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode());
|
||||
|
||||
if (formData.size() > 0) forms.setFormData(formData);
|
||||
.setActionUri(action)
|
||||
.setClientSessionCode(clientSessionCode.getCode());
|
||||
if (error != null) forms.setError(error);
|
||||
|
||||
return forms.createLoginTotp();
|
||||
}
|
||||
|
||||
public Response challenge(AuthenticatorContext context) {
|
||||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
|
||||
return challenge(context, formData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(UserModel user) {
|
||||
return user.configuredForCredentialType(UserCredentialModel.TOTP);
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return session.users().configuredForCredentialType(UserCredentialModel.TOTP, realm, user) && user.isTotp();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
|
|||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -48,6 +50,25 @@ public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
|
|||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceType() {
|
||||
return UserCredentialModel.TOTP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED,
|
||||
AuthenticationExecutionModel.Requirement.OPTIONAL,
|
||||
AuthenticationExecutionModel.Requirement.DISABLED};
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "Credential Validation";
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
package org.keycloak.authentication.authenticators;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorContext;
|
||||
import org.keycloak.constants.KerberosConstants;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.keycloak.util.HtmlUtils.escapeAttribute;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Authenticator{
|
||||
public static final String KERBEROS_DISABLED = "kerberos_disabled";
|
||||
protected static Logger logger = Logger.getLogger(SpnegoAuthenticator.class);
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isAlreadyChallenged(AuthenticatorContext context) {
|
||||
UserSessionModel.AuthenticatorStatus status = context.getClientSession().getAuthenticators().get(context.getExecution().getId());
|
||||
if (status == null) return false;
|
||||
return status == UserSessionModel.AuthenticatorStatus.CHALLENGED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticatorContext context) {
|
||||
HttpRequest request = context.getHttpRequest();
|
||||
String authHeader = request.getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||
if (isAction(context, KERBEROS_DISABLED)) {
|
||||
context.attempted();
|
||||
return;
|
||||
}
|
||||
// Case when we don't yet have any Negotiate header
|
||||
if (authHeader == null) {
|
||||
if (isAlreadyChallenged(context)) {
|
||||
context.attempted();
|
||||
return;
|
||||
}
|
||||
Response challenge = challengeNegotiation(context, null);
|
||||
context.forceChallenge(challenge);
|
||||
return;
|
||||
}
|
||||
|
||||
String[] tokens = authHeader.split(" ");
|
||||
if (tokens.length == 0) { // assume not supported
|
||||
logger.debug("Invalid length of tokens: " + tokens.length);
|
||||
context.attempted();
|
||||
return;
|
||||
}
|
||||
if (!KerberosConstants.NEGOTIATE.equalsIgnoreCase(tokens[0])) {
|
||||
logger.debug("Unknown scheme " + tokens[0]);
|
||||
context.attempted();
|
||||
return;
|
||||
}
|
||||
if (tokens.length != 2) {
|
||||
context.failure(AuthenticationProcessor.Error.INVALID_CREDENTIALS);
|
||||
return;
|
||||
}
|
||||
|
||||
String spnegoToken = tokens[1];
|
||||
UserCredentialModel spnegoCredential = UserCredentialModel.kerberos(spnegoToken);
|
||||
|
||||
CredentialValidationOutput output = context.getSession().users().validCredentials(context.getRealm(), spnegoCredential);
|
||||
|
||||
if (output.getAuthStatus() == CredentialValidationOutput.Status.AUTHENTICATED) {
|
||||
context.setUser(output.getAuthenticatedUser());
|
||||
if (output.getState() != null && !output.getState().isEmpty()) {
|
||||
for (Map.Entry<String, String> entry : output.getState().entrySet()) {
|
||||
context.getClientSession().setUserSessionNote(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
context.success();
|
||||
} else if (output.getAuthStatus() == CredentialValidationOutput.Status.CONTINUE) {
|
||||
String spnegoResponseToken = (String) output.getState().get(KerberosConstants.RESPONSE_TOKEN);
|
||||
Response challenge = challengeNegotiation(context, spnegoResponseToken);
|
||||
context.challenge(challenge);
|
||||
} else {
|
||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||
context.failure(AuthenticationProcessor.Error.INVALID_CREDENTIALS);
|
||||
}
|
||||
}
|
||||
|
||||
private Response challengeNegotiation(AuthenticatorContext context, final String negotiateToken) {
|
||||
String negotiateHeader = negotiateToken == null ? KerberosConstants.NEGOTIATE : KerberosConstants.NEGOTIATE + " " + negotiateToken;
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Sending back " + HttpHeaders.WWW_AUTHENTICATE + ": " + negotiateHeader);
|
||||
}
|
||||
if (context.getExecution().isRequired()) {
|
||||
return context.getSession().getProvider(LoginFormsProvider.class)
|
||||
.setStatus(Response.Status.UNAUTHORIZED)
|
||||
.setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader)
|
||||
.setError(Messages.KERBEROS_NOT_ENABLED).createErrorPage();
|
||||
} else {
|
||||
return optionalChallengeRedirect(context, negotiateHeader);
|
||||
}
|
||||
}
|
||||
|
||||
// This is used for testing only. Selenium will execute the HTML challenge sent back which results in the javascript
|
||||
// redirecting. Our old Selenium tests expect that the current URL will be the original openid redirect.
|
||||
public static boolean bypassChallengeJavascript = false;
|
||||
|
||||
/**
|
||||
* 401 challenge sent back that bypasses
|
||||
* @param context
|
||||
* @param negotiateHeader
|
||||
* @return
|
||||
*/
|
||||
protected Response optionalChallengeRedirect(AuthenticatorContext context, String negotiateHeader) {
|
||||
ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
|
||||
code.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||
URI action = getActionUrl(context, code, KERBEROS_DISABLED);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append("<HTML>");
|
||||
builder.append("<HEAD>");
|
||||
|
||||
builder.append("<TITLE>Kerberos Unsupported</TITLE>");
|
||||
builder.append("</HEAD>");
|
||||
if (bypassChallengeJavascript) {
|
||||
builder.append("<BODY>");
|
||||
|
||||
} else {
|
||||
builder.append("<BODY Onload=\"document.forms[0].submit()\">");
|
||||
}
|
||||
builder.append("<FORM METHOD=\"POST\" ACTION=\"" + action.toString() + "\">");
|
||||
builder.append("<NOSCRIPT>");
|
||||
builder.append("<P>JavaScript is disabled. We strongly recommend to enable it. You were unable to login via Kerberos. Click the button below to login via an alternative method .</P>");
|
||||
builder.append("<INPUT name=\"continue\" TYPE=\"SUBMIT\" VALUE=\"CONTINUE\" />");
|
||||
builder.append("</NOSCRIPT>");
|
||||
|
||||
builder.append("</FORM></BODY></HTML>");
|
||||
return Response.status(Response.Status.UNAUTHORIZED)
|
||||
.header(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader)
|
||||
.type(MediaType.TEXT_HTML_TYPE)
|
||||
.entity(builder.toString()).build();
|
||||
}
|
||||
|
||||
protected Response formChallenge(AuthenticatorContext context, String negotiateHeader) {
|
||||
ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
|
||||
code.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||
URI action = getActionUrl(context, code, KERBEROS_DISABLED);
|
||||
return context.getSession().getProvider(LoginFormsProvider.class)
|
||||
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
|
||||
.setActionUri(action)
|
||||
.setStatus(Response.Status.UNAUTHORIZED)
|
||||
.setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader)
|
||||
.setUser(context.getUser())
|
||||
.createForm("bypass_kerberos.ftl", new HashMap<String, Object>());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequiredAction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package org.keycloak.authentication.authenticators;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "auth-spnego";
|
||||
|
||||
@Override
|
||||
public Authenticator create(AuthenticatorModel model) {
|
||||
return new SpnegoAuthenticator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator create(KeycloakSession session) {
|
||||
throw new IllegalStateException("illegal call");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceType() {
|
||||
return UserCredentialModel.KERBEROS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED,
|
||||
AuthenticationExecutionModel.Requirement.DISABLED};
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "Complete Authenticator";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "SPNEGO";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Initiates the SPNEGO protocol. Most often used with Kerberos.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -130,7 +130,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
|||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
String redirect = clientSession.getRedirectUri();
|
||||
String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
|
||||
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
|
||||
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
|
||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, accessCode.getCode());
|
||||
log.debugv("redirectAccessCode: state: {0}", state);
|
||||
if (state != null)
|
||||
|
|
|
@ -217,6 +217,12 @@ public class TokenManager {
|
|||
}
|
||||
}
|
||||
clientSession.setProtocolMappers(requestedProtocolMappers);
|
||||
|
||||
Map<String, String> transferredNotes = clientSession.getUserSessionNotes();
|
||||
for (Map.Entry<String, String> entry : transferredNotes.entrySet()) {
|
||||
session.setNote(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void dettachClientSession(UserSessionProvider sessions, RealmModel realm, ClientSessionModel clientSession) {
|
||||
|
|
|
@ -6,7 +6,6 @@ import org.jboss.resteasy.spi.HttpRequest;
|
|||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.authenticators.AuthenticationFlow;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
|
@ -20,6 +19,7 @@ import org.keycloak.models.IdentityProviderModel;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredCredentialModel;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
|
@ -44,6 +44,7 @@ import java.util.List;
|
|||
public class AuthorizationEndpoint {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AuthorizationEndpoint.class);
|
||||
public static final String CODE_AUTH_TYPE = "code";
|
||||
|
||||
private enum Action {
|
||||
REGISTER, CODE
|
||||
|
@ -220,7 +221,7 @@ public class AuthorizationEndpoint {
|
|||
clientSession = session.sessions().createClientSession(realm, client);
|
||||
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
clientSession.setRedirectUri(redirectUri);
|
||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||
clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
|
||||
clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, responseType);
|
||||
clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUriParam);
|
||||
|
@ -247,17 +248,20 @@ public class AuthorizationEndpoint {
|
|||
return buildRedirectToIdentityProvider(idpHint, accessCode);
|
||||
}
|
||||
|
||||
return oldBrowserAuthentication(accessCode);
|
||||
return newBrowserAuthentication(accessCode);
|
||||
}
|
||||
|
||||
protected Response newBrowserAuthentication(String accessCode) {
|
||||
String flowId = null;
|
||||
for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) {
|
||||
if (flow.getAlias().equals("browser")) {
|
||||
flowId = flow.getId();
|
||||
break;
|
||||
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
||||
for (IdentityProviderModel identityProvider : identityProviders) {
|
||||
if (identityProvider.isAuthenticateByDefault()) {
|
||||
return buildRedirectToIdentityProvider(identityProvider.getAlias(), accessCode);
|
||||
}
|
||||
}
|
||||
clientSession.setNote(Details.AUTH_TYPE, CODE_AUTH_TYPE);
|
||||
|
||||
AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
|
||||
String flowId = flow.getId();
|
||||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||
processor.setClientSession(clientSession)
|
||||
.setFlowId(flowId)
|
||||
|
@ -269,7 +273,26 @@ public class AuthorizationEndpoint {
|
|||
.setUriInfo(uriInfo)
|
||||
.setRequest(request);
|
||||
|
||||
return processor.authenticate();
|
||||
Response challenge = null;
|
||||
try {
|
||||
challenge = processor.authenticateOnly();
|
||||
} catch (Exception e) {
|
||||
return processor.handleBrowserException(e);
|
||||
}
|
||||
|
||||
if (challenge != null && prompt != null && prompt.equals("none")) {
|
||||
if (processor.isUserSessionCreated()) {
|
||||
session.sessions().removeUserSession(realm, processor.getUserSession());
|
||||
}
|
||||
OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers, event);
|
||||
return oauth.cancelLogin(clientSession);
|
||||
}
|
||||
|
||||
if (challenge == null) {
|
||||
return processor.finishAuthentication();
|
||||
} else {
|
||||
return challenge;
|
||||
}
|
||||
}
|
||||
|
||||
protected Response oldBrowserAuthentication(String accessCode) {
|
||||
|
|
|
@ -191,7 +191,7 @@ public class TokenEndpoint {
|
|||
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
event.detail(Details.CODE_ID, clientSession.getId());
|
||||
if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN)) {
|
||||
if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name())) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
throw new ErrorResponseException("invalid_grant", "Code is expired", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
|
12
services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
Normal file → Executable file
12
services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
Normal file → Executable file
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.services;
|
||||
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -17,6 +18,8 @@ public class DefaultKeycloakContext implements KeycloakContext {
|
|||
|
||||
private ClientModel client;
|
||||
|
||||
private ClientConnection connection;
|
||||
|
||||
@Override
|
||||
public UriInfo getUri() {
|
||||
return ResteasyProviderFactory.getContextData(UriInfo.class);
|
||||
|
@ -47,4 +50,13 @@ public class DefaultKeycloakContext implements KeycloakContext {
|
|||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConnection(ClientConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,15 +128,20 @@ public class Urls {
|
|||
}
|
||||
|
||||
public static URI loginActionUpdatePassword(URI baseUri, String realmId) {
|
||||
return requiredActionsBase(baseUri).path(LoginActionsService.class, "updatePassword").build(realmId);
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "updatePassword").build(realmId);
|
||||
}
|
||||
|
||||
public static URI loginActionUpdateTotp(URI baseUri, String realmId) {
|
||||
return requiredActionsBase(baseUri).path(LoginActionsService.class, "updateTotp").build(realmId);
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "updateTotp").build(realmId);
|
||||
}
|
||||
|
||||
public static UriBuilder requiredActionBase(URI baseUri) {
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "requiredAction");
|
||||
}
|
||||
|
||||
|
||||
public static URI loginActionUpdateProfile(URI baseUri, String realmId) {
|
||||
return requiredActionsBase(baseUri).path(LoginActionsService.class, "updateProfile").build(realmId);
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "updateProfile").build(realmId);
|
||||
}
|
||||
|
||||
public static URI loginActionEmailVerification(URI baseUri, String realmId) {
|
||||
|
@ -144,7 +149,7 @@ public class Urls {
|
|||
}
|
||||
|
||||
public static UriBuilder loginActionEmailVerificationBuilder(URI baseUri) {
|
||||
return requiredActionsBase(baseUri).path(LoginActionsService.class, "emailVerification");
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "emailVerification");
|
||||
}
|
||||
|
||||
public static URI loginPasswordReset(URI baseUri, String realmId) {
|
||||
|
@ -152,7 +157,7 @@ public class Urls {
|
|||
}
|
||||
|
||||
public static UriBuilder loginPasswordResetBuilder(URI baseUri) {
|
||||
return requiredActionsBase(baseUri).path(LoginActionsService.class, "passwordReset");
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "passwordReset");
|
||||
}
|
||||
|
||||
public static URI loginUsernameReminder(URI baseUri, String realmId) {
|
||||
|
@ -160,7 +165,7 @@ public class Urls {
|
|||
}
|
||||
|
||||
public static UriBuilder loginUsernameReminderBuilder(URI baseUri) {
|
||||
return requiredActionsBase(baseUri).path(LoginActionsService.class, "usernameReminder");
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "usernameReminder");
|
||||
}
|
||||
|
||||
public static String realmIssuer(URI baseUri, String realmId) {
|
||||
|
@ -172,11 +177,11 @@ public class Urls {
|
|||
}
|
||||
|
||||
public static URI realmLoginAction(URI baseUri, String realmId) {
|
||||
return requiredActionsBase(baseUri).path(LoginActionsService.class, "processLogin").build(realmId);
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "processLogin").build(realmId);
|
||||
}
|
||||
|
||||
public static URI realmLoginPage(URI baseUri, String realmId) {
|
||||
return requiredActionsBase(baseUri).path(LoginActionsService.class, "loginPage").build(realmId);
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "loginPage").build(realmId);
|
||||
}
|
||||
|
||||
private static UriBuilder realmLogout(URI baseUri) {
|
||||
|
@ -184,11 +189,11 @@ public class Urls {
|
|||
}
|
||||
|
||||
public static URI realmRegisterAction(URI baseUri, String realmId) {
|
||||
return requiredActionsBase(baseUri).path(LoginActionsService.class, "processRegister").build(realmId);
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "processRegister").build(realmId);
|
||||
}
|
||||
|
||||
public static URI realmRegisterPage(URI baseUri, String realmId) {
|
||||
return requiredActionsBase(baseUri).path(LoginActionsService.class, "registerPage").build(realmId);
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "registerPage").build(realmId);
|
||||
}
|
||||
|
||||
public static URI realmInstalledAppUrnCallback(URI baseUri, String realmId) {
|
||||
|
@ -196,7 +201,7 @@ public class Urls {
|
|||
}
|
||||
|
||||
public static URI realmOauthAction(URI baseUri, String realmId) {
|
||||
return requiredActionsBase(baseUri).path(LoginActionsService.class, "processConsent").build(realmId);
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "processConsent").build(realmId);
|
||||
}
|
||||
|
||||
public static String localeCookiePath(URI baseUri, String realmName){
|
||||
|
@ -207,7 +212,7 @@ public class Urls {
|
|||
return themeBase(baseUri).path(Version.RESOURCES_VERSION).build();
|
||||
}
|
||||
|
||||
private static UriBuilder requiredActionsBase(URI baseUri) {
|
||||
private static UriBuilder loginActionsBase(URI baseUri) {
|
||||
return realmBase(baseUri).path(RealmsResource.class, "getLoginActionsService");
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import java.io.IOException;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@Deprecated
|
||||
public class ClientConnectionFilter implements Filter {
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.services.filters;
|
||||
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
|
@ -26,11 +27,29 @@ public class KeycloakSessionServletFilter implements Filter {
|
|||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest)servletRequest;
|
||||
final HttpServletRequest request = (HttpServletRequest)servletRequest;
|
||||
|
||||
KeycloakSessionFactory sessionFactory = (KeycloakSessionFactory) servletRequest.getServletContext().getAttribute(KeycloakSessionFactory.class.getName());
|
||||
KeycloakSession session = sessionFactory.create();
|
||||
ResteasyProviderFactory.pushContext(KeycloakSession.class, session);
|
||||
ClientConnection connection = new ClientConnection() {
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
return request.getRemoteAddr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteHost() {
|
||||
return request.getRemoteHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReportPort() {
|
||||
return request.getRemotePort();
|
||||
}
|
||||
};
|
||||
session.getContext().setConnection(connection);
|
||||
ResteasyProviderFactory.pushContext(ClientConnection.class, connection);
|
||||
|
||||
KeycloakTransaction tx = session.getTransaction();
|
||||
ResteasyProviderFactory.pushContext(KeycloakTransaction.class, tx);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue