Merge pull request #1378 from patriot1burke/master

auth spi
This commit is contained in:
Bill Burke 2015-06-15 09:50:05 -04:00
commit 88f14b39a7
131 changed files with 3870 additions and 940 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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();

View file

@ -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>

View file

@ -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>

View file

@ -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";

View 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();
}

View file

@ -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'
})

View file

@ -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();
});

View file

@ -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)) {

View file

@ -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'
}
});
});

View file

@ -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>

View file

@ -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>

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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>

View 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>

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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,

View 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);
}

View file

@ -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);

View file

@ -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));

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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);
//
}
}

View file

@ -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);
}

View file

@ -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());

View file

@ -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);

View file

@ -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

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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;
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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());

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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();

View file

@ -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();
}

View file

@ -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();
}

View 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();
}
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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);
}

View 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;
}
}

View file

@ -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() {
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View 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();
}
}

View 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();
}
}

View file

@ -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;
}
}

View file

@ -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);
}
*/
}

View file

@ -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;
}

View file

@ -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";

View file

@ -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

View file

@ -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() {

View file

@ -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

View file

@ -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";

View file

@ -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;
}

View file

@ -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";

View file

@ -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

View file

@ -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";

View file

@ -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() {
}
}

View file

@ -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;
}
}

View file

@ -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)

View file

@ -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) {

View file

@ -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) {

View file

@ -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);
}

View 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;
}
}

View file

@ -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");
}

View file

@ -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 {

View file

@ -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