kerberos fixes
This commit is contained in:
parent
392fa21f1e
commit
c51cc4703b
37 changed files with 733 additions and 143 deletions
2
events/api/src/main/java/org/keycloak/events/EventBuilder.java
Normal file → Executable file
2
events/api/src/main/java/org/keycloak/events/EventBuilder.java
Normal file → Executable file
|
@ -140,7 +140,9 @@ public class EventBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void error(String error) {
|
public void error(String error) {
|
||||||
|
if (!event.getType().name().endsWith("_ERROR")) {
|
||||||
event.setType(EventType.valueOf(event.getType().name() + "_ERROR"));
|
event.setType(EventType.valueOf(event.getType().name() + "_ERROR"));
|
||||||
|
}
|
||||||
event.setError(error);
|
event.setError(error);
|
||||||
send();
|
send();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1045,6 +1045,16 @@ module.config([ '$routeProvider', function($routeProvider) {
|
||||||
},
|
},
|
||||||
controller : 'ProtocolListCtrl'
|
controller : 'ProtocolListCtrl'
|
||||||
})
|
})
|
||||||
|
.when('/realms/:realm/authentication', {
|
||||||
|
templateUrl : resourceUrl + '/partials/authentication-flows.html',
|
||||||
|
resolve : {
|
||||||
|
realm : function(RealmLoader) {
|
||||||
|
return RealmLoader();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
controller : 'AuthenticationFlowsCtrl'
|
||||||
|
})
|
||||||
|
|
||||||
.when('/server-info', {
|
.when('/server-info', {
|
||||||
templateUrl : resourceUrl + '/partials/server-info.html'
|
templateUrl : resourceUrl + '/partials/server-info.html'
|
||||||
})
|
})
|
||||||
|
|
|
@ -1572,6 +1572,45 @@ module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, id
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.controller('AuthenticationFlowsCtrl', function($scope, realm, AuthenticationExecutions, Notifications, Dialog, $location) {
|
||||||
|
$scope.realm = realm;
|
||||||
|
var setupForm = function() {
|
||||||
|
AuthenticationExecutions.query({realm: realm.realm, alias: 'browser'}, function(data) {
|
||||||
|
$scope.executions = data;
|
||||||
|
$scope.flowmax = 0;
|
||||||
|
for (var i = 0; i < $scope.executions.length; i++ ) {
|
||||||
|
execution = $scope.executions[i];
|
||||||
|
if (execution.requirementChoices.length > $scope.flowmax) {
|
||||||
|
$scope.flowmax = execution.requirementChoices.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0; i < $scope.executions.length; i++ ) {
|
||||||
|
execution = $scope.executions[i];
|
||||||
|
execution.empties = [];
|
||||||
|
for (j = 0; j < $scope.flowmax - execution.requirementChoices.length; j++) {
|
||||||
|
execution.empties.push(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.updateExecution = function(execution) {
|
||||||
|
var copy = angular.copy(execution);
|
||||||
|
delete copy.empties;
|
||||||
|
AuthenticationExecutions.update({realm: realm.realm, alias: 'browser'}, copy, function() {
|
||||||
|
Notifications.success("Auth requirement updated");
|
||||||
|
setupForm();
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
setupForm();
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1073,3 +1073,15 @@ module.factory('IdentityProviderMapper', function($resource) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.factory('AuthenticationExecutions', function($resource) {
|
||||||
|
return $resource(authUrl + '/admin/realms/:realm/authentication-flows/flow/:alias/executions', {
|
||||||
|
realm : '@realm',
|
||||||
|
alias : '@alias'
|
||||||
|
}, {
|
||||||
|
update : {
|
||||||
|
method : 'PUT'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||||
|
<h1><strong>Authentication Flows</strong> {{realm.realm|capitalize}}</h1>
|
||||||
|
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<!--
|
||||||
|
<tr>
|
||||||
|
<th class="kc-table-actions" colspan="5">
|
||||||
|
<div class="form-inline">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" placeholder="Search..." data-ng-model="search.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
|
||||||
|
<div class="input-group-addon">
|
||||||
|
<i class="fa fa-search" type="submit"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
-->
|
||||||
|
<tr data-ng-hide="executions.length == 0">
|
||||||
|
<th colspan="2">Auth Type</th>
|
||||||
|
<th colspan="{{flowmax}}">Requirement</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="execution in executions">
|
||||||
|
<td ng-show="execution.subFlow"></td>
|
||||||
|
<td><h2>{{execution.referenceType}}</h2></td>
|
||||||
|
<td ng-hide="execution.subFlow"></td>
|
||||||
|
<td ng-repeat="choice in execution.requirementChoices">
|
||||||
|
<!--
|
||||||
|
<div class="dropdown pull-left">
|
||||||
|
<select class="form-control"
|
||||||
|
ng-model="execution.requirement"
|
||||||
|
ng-options="choice for choice in execution.requirementChoices">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
<div ng-repeat="choice in execution.requirementChoices">
|
||||||
|
<label >
|
||||||
|
<input type="radio" ng-model="execution.requirement" ng-value="choice">
|
||||||
|
{{choice}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
<label >
|
||||||
|
<input type="radio" ng-model="execution.requirement" ng-value="choice" ng-change="updateExecution(execution)">
|
||||||
|
{{choice}}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td ng-repeat="emptee in execution.empties"></td>
|
||||||
|
</tr>
|
||||||
|
<tr data-ng-show="executions.length == 0">
|
||||||
|
<td>No executions available</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kc-menu></kc-menu>
|
|
@ -16,6 +16,7 @@
|
||||||
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'roles' || path[2] == 'default-roles' || (path[1] == 'role' && path[3] != 'clients')) && 'active'"><a href="#/realms/{{realm.realm}}/roles">Roles</a></li>
|
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == '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[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] == '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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
25
forms/common-themes/src/main/resources/theme/base/login/bypass_kerberos.ftl
Executable file
25
forms/common-themes/src/main/resources/theme/base/login/bypass_kerberos.ftl
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
<#import "template.ftl" as layout>
|
||||||
|
<@layout.registrationLayout displayMessage=false; section>
|
||||||
|
<#if section = "title">
|
||||||
|
${msg("kerberosNotConfiguredTitle")}
|
||||||
|
<#elseif section = "header">
|
||||||
|
${msg("kerberosNotConfigured")}
|
||||||
|
<#elseif section = "form">
|
||||||
|
<div id="kc-info-message">
|
||||||
|
<!-- <h3>${msg("kerberosNotConfigured")}</h3> -->
|
||||||
|
<p class="instruction">${msg("bypassKerberosDetail")}</p>
|
||||||
|
<form class="form-actions" action="${url.loginAction}" method="POST">
|
||||||
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
|
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||||
|
<div class="${properties.kcFormButtonsWrapperClass!}">
|
||||||
|
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="continue" id="kc-login" type="submit" value="${msg("doContinue")}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<#if client?? && client.baseUrl?has_content>
|
||||||
|
<p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
|
@ -6,8 +6,13 @@ doYes=Ja
|
||||||
doNo=Nein
|
doNo=Nein
|
||||||
doAccept=Accept
|
doAccept=Accept
|
||||||
doDecline=Decline
|
doDecline=Decline
|
||||||
|
doContinue=Continue
|
||||||
doForgotPassword=Passwort vergessen?
|
doForgotPassword=Passwort vergessen?
|
||||||
doClickHere=hier klicken
|
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}
|
registerWithTitle=Registrierung bei {0}
|
||||||
registerWithTitleHtml=Registrierung bei <strong>{0}</strong>
|
registerWithTitleHtml=Registrierung bei <strong>{0}</strong>
|
||||||
|
|
|
@ -4,11 +4,15 @@ doCancel=Cancel
|
||||||
doSubmit=Submit
|
doSubmit=Submit
|
||||||
doYes=Yes
|
doYes=Yes
|
||||||
doNo=No
|
doNo=No
|
||||||
|
doContinue=Continue
|
||||||
doAccept=Accept
|
doAccept=Accept
|
||||||
doDecline=Decline
|
doDecline=Decline
|
||||||
doForgotPassword=Forgot Password?
|
doForgotPassword=Forgot Password?
|
||||||
doClickHere=Click here
|
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}
|
registerWithTitle=Register with {0}
|
||||||
registerWithTitleHtml=Register with <strong>{0}</strong>
|
registerWithTitleHtml=Register with <strong>{0}</strong>
|
||||||
loginTitle=Log in to {0}
|
loginTitle=Log in to {0}
|
||||||
|
|
|
@ -6,8 +6,13 @@ doYes=Si
|
||||||
doNo=No
|
doNo=No
|
||||||
doAccept=Accept
|
doAccept=Accept
|
||||||
doDecline=Decline
|
doDecline=Decline
|
||||||
|
doContinue=Continue
|
||||||
doForgotPassword=Password Dimenticata?
|
doForgotPassword=Password Dimenticata?
|
||||||
doClickHere=Clicca qui
|
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}
|
registerWithTitle=Registrati come {0}
|
||||||
registerWithTitleHtml=Registrati come <strong>{0}</strong>
|
registerWithTitleHtml=Registrati come <strong>{0}</strong>
|
||||||
|
|
|
@ -6,8 +6,13 @@ doYes=Sim
|
||||||
doAccept=Accept
|
doAccept=Accept
|
||||||
doDecline=Decline
|
doDecline=Decline
|
||||||
doNo=N\u00E3o
|
doNo=N\u00E3o
|
||||||
|
doContinue=Continue
|
||||||
doForgotPassword=Esqueceu sua senha?
|
doForgotPassword=Esqueceu sua senha?
|
||||||
doClickHere=Clique aqui
|
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}
|
registerWithTitle=Registre-se com {0}
|
||||||
registerWithTitleHtml=Registre-se com <strong>{0}</strong>
|
registerWithTitleHtml=Registre-se com <strong>{0}</strong>
|
||||||
|
|
|
@ -314,6 +314,9 @@ import java.util.concurrent.TimeUnit;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.warn("Failed to load properties", e);
|
logger.warn("Failed to load properties", e);
|
||||||
}
|
}
|
||||||
|
if (client != null) {
|
||||||
|
attributes.put("client", new ClientBean(client));
|
||||||
|
}
|
||||||
|
|
||||||
Properties messagesBundle;
|
Properties messagesBundle;
|
||||||
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, session.getContext().getRequestHeaders());
|
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, session.getContext().getRequestHeaders());
|
||||||
|
|
|
@ -1289,6 +1289,7 @@ public class RealmAdapter implements RealmModel {
|
||||||
AuthenticationExecutionModel model = entityToModel(entity);
|
AuthenticationExecutionModel model = entityToModel(entity);
|
||||||
executions.add(model);
|
executions.add(model);
|
||||||
}
|
}
|
||||||
|
Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
|
||||||
return executions;
|
return executions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1588,6 +1588,7 @@ public class RealmAdapter implements RealmModel {
|
||||||
AuthenticationExecutionModel model = entityToModel(entity);
|
AuthenticationExecutionModel model = entityToModel(entity);
|
||||||
executions.add(model);
|
executions.add(model);
|
||||||
}
|
}
|
||||||
|
Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
|
||||||
return executions;
|
return executions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1319,6 +1319,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
AuthenticationExecutionModel model = entityToModel(entity);
|
AuthenticationExecutionModel model = entityToModel(entity);
|
||||||
executions.add(model);
|
executions.add(model);
|
||||||
}
|
}
|
||||||
|
Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
|
||||||
return executions;
|
return executions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ public class AuthenticationProcessor {
|
||||||
public static enum Status {
|
public static enum Status {
|
||||||
SUCCESS,
|
SUCCESS,
|
||||||
CHALLENGE,
|
CHALLENGE,
|
||||||
|
FORCE_CHALLENGE,
|
||||||
FAILURE_CHALLENGE,
|
FAILURE_CHALLENGE,
|
||||||
FAILED,
|
FAILED,
|
||||||
ATTEMPTED
|
ATTEMPTED
|
||||||
|
@ -214,6 +215,12 @@ public class AuthenticationProcessor {
|
||||||
this.status = Status.CHALLENGE;
|
this.status = Status.CHALLENGE;
|
||||||
this.challenge = challenge;
|
this.challenge = challenge;
|
||||||
|
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void forceChallenge(Response challenge) {
|
||||||
|
this.status = Status.FORCE_CHALLENGE;
|
||||||
|
this.challenge = challenge;
|
||||||
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void failureChallenge(Error error, Response challenge) {
|
public void failureChallenge(Error error, Response challenge) {
|
||||||
|
@ -437,7 +444,6 @@ public class AuthenticationProcessor {
|
||||||
}
|
}
|
||||||
List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(flowId);
|
List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(flowId);
|
||||||
if (executions == null) return null;
|
if (executions == null) return null;
|
||||||
Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
|
|
||||||
Response alternativeChallenge = null;
|
Response alternativeChallenge = null;
|
||||||
AuthenticationExecutionModel challengedAlternativeExecution = null;
|
AuthenticationExecutionModel challengedAlternativeExecution = null;
|
||||||
boolean alternativeSuccessful = false;
|
boolean alternativeSuccessful = false;
|
||||||
|
@ -513,6 +519,9 @@ public class AuthenticationProcessor {
|
||||||
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.FAILED);
|
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.FAILED);
|
||||||
if (context.challenge != null) return context.challenge;
|
if (context.challenge != null) return context.challenge;
|
||||||
throw new AuthException(context.error);
|
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) {
|
} else if (result == Status.CHALLENGE) {
|
||||||
logger.debugv("authenticator CHALLENGE: {0}", authenticatorModel.getProviderId());
|
logger.debugv("authenticator CHALLENGE: {0}", authenticatorModel.getProviderId());
|
||||||
if (model.isRequired() || (model.isOptional() && configuredFor)) {
|
if (model.isRequired() || (model.isOptional() && configuredFor)) {
|
||||||
|
|
|
@ -61,6 +61,9 @@ public interface AuthenticatorContext {
|
||||||
void failure(AuthenticationProcessor.Error error);
|
void failure(AuthenticationProcessor.Error error);
|
||||||
void failure(AuthenticationProcessor.Error error, Response response);
|
void failure(AuthenticationProcessor.Error error, Response response);
|
||||||
void challenge(Response challenge);
|
void challenge(Response challenge);
|
||||||
|
|
||||||
|
void forceChallenge(Response challenge);
|
||||||
|
|
||||||
void failureChallenge(AuthenticationProcessor.Error error, Response challenge);
|
void failureChallenge(AuthenticationProcessor.Error error, Response challenge);
|
||||||
void attempted();
|
void attempted();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package org.keycloak.authentication;
|
package org.keycloak.authentication;
|
||||||
|
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticatorModel;
|
import org.keycloak.models.AuthenticatorModel;
|
||||||
import org.keycloak.provider.ConfiguredProvider;
|
import org.keycloak.provider.ConfiguredProvider;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
|
@ -13,4 +16,21 @@ public interface AuthenticatorFactory extends ProviderFactory<Authenticator>, Co
|
||||||
String getDisplayCategory();
|
String getDisplayCategory();
|
||||||
String getDisplayType();
|
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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,31 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class AuthenticatorUtil {
|
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) {
|
public static AuthenticationExecutionModel findExecutionByAuthenticator(RealmModel realm, String flowId, String authProviderId) {
|
||||||
for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
|
for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
|
||||||
if (model.isAutheticatorFlow()) {
|
if (model.isAutheticatorFlow()) {
|
||||||
|
|
|
@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticatorModel;
|
import org.keycloak.models.AuthenticatorModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -47,6 +49,23 @@ public class CookieAuthenticatorFactory implements AuthenticatorFactory {
|
||||||
return PROVIDER_ID;
|
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
|
@Override
|
||||||
public String getDisplayCategory() {
|
public String getDisplayCategory() {
|
||||||
return "Complete Authenticator";
|
return "Complete Authenticator";
|
||||||
|
|
|
@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticatorModel;
|
import org.keycloak.models.AuthenticatorModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
import java.util.List;
|
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
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticatorModel;
|
import org.keycloak.models.AuthenticatorModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -48,6 +50,24 @@ public class LoginFormPasswordAuthenticatorFactory implements AuthenticatorFacto
|
||||||
return PROVIDER_ID;
|
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
|
@Override
|
||||||
public String getDisplayCategory() {
|
public String getDisplayCategory() {
|
||||||
return "Credential Validation";
|
return "Credential Validation";
|
||||||
|
|
|
@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticatorModel;
|
import org.keycloak.models.AuthenticatorModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -48,6 +50,26 @@ public class LoginFormUsernameAuthenticatorFactory implements AuthenticatorFacto
|
||||||
return PROVIDER_ID;
|
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
|
@Override
|
||||||
public String getDisplayCategory() {
|
public String getDisplayCategory() {
|
||||||
return "User Validation";
|
return "User Validation";
|
||||||
|
|
|
@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticatorModel;
|
import org.keycloak.models.AuthenticatorModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -48,6 +50,25 @@ public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
|
||||||
return PROVIDER_ID;
|
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
|
@Override
|
||||||
public String getDisplayCategory() {
|
public String getDisplayCategory() {
|
||||||
return "Credential Validation";
|
return "Credential Validation";
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
package org.keycloak.authentication.authenticators;
|
|
||||||
|
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No auth, but it sets a required action.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class SetRequiredActionAuthenticator implements Authenticator {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requiresUser() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void authenticate(AuthenticatorContext context) {
|
|
||||||
UserModel user = context.getUser();
|
|
||||||
if (user == null) {
|
|
||||||
throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.UNKNOWN_USER);
|
|
||||||
}
|
|
||||||
user.addRequiredAction(context.getAuthenticatorModel().getConfig().get("required.action"));
|
|
||||||
context.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRequiredAction() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
package org.keycloak.authentication.authenticators;
|
|
||||||
|
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.authentication.Authenticator;
|
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
|
||||||
import org.keycloak.models.AuthenticatorModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class SetRequiredActionAuthenticatorFactory implements AuthenticatorFactory {
|
|
||||||
public static final String PROVIDER_ID = "auth-set-required-action";
|
|
||||||
static SetRequiredActionAuthenticator SINGLETON = new SetRequiredActionAuthenticator();
|
|
||||||
@Override
|
|
||||||
public Authenticator create(AuthenticatorModel model) {
|
|
||||||
return SINGLETON;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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 getId() {
|
|
||||||
return PROVIDER_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDisplayCategory() {
|
|
||||||
return "Action";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDisplayType() {
|
|
||||||
return "Set Required Action";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHelpText() {
|
|
||||||
return "Doesn't do any authentication. Instead it just sets a configured required action for the user.";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ProviderConfigProperty> getConfigProperties() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,22 +8,31 @@ import org.keycloak.authentication.AuthenticatorContext;
|
||||||
import org.keycloak.constants.KerberosConstants;
|
import org.keycloak.constants.KerberosConstants;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.login.LoginFormsProvider;
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
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.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.keycloak.util.HtmlUtils.escapeAttribute;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Authenticator{
|
public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Authenticator{
|
||||||
|
public static final String KERBEROS_DISABLED = "kerberos_disabled";
|
||||||
protected static Logger logger = Logger.getLogger(SpnegoAuthenticator.class);
|
protected static Logger logger = Logger.getLogger(SpnegoAuthenticator.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -41,7 +50,10 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au
|
||||||
public void authenticate(AuthenticatorContext context) {
|
public void authenticate(AuthenticatorContext context) {
|
||||||
HttpRequest request = context.getHttpRequest();
|
HttpRequest request = context.getHttpRequest();
|
||||||
String authHeader = request.getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
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
|
// Case when we don't yet have any Negotiate header
|
||||||
if (authHeader == null) {
|
if (authHeader == null) {
|
||||||
if (isAlreadyChallenged(context)) {
|
if (isAlreadyChallenged(context)) {
|
||||||
|
@ -49,7 +61,7 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Response challenge = challengeNegotiation(context, null);
|
Response challenge = challengeNegotiation(context, null);
|
||||||
context.challenge(challenge);
|
context.forceChallenge(challenge);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,11 +110,68 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Sending back " + HttpHeaders.WWW_AUTHENTICATE + ": " + negotiateHeader);
|
logger.trace("Sending back " + HttpHeaders.WWW_AUTHENTICATE + ": " + negotiateHeader);
|
||||||
}
|
}
|
||||||
LoginFormsProvider loginForm = loginForm(context);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loginForm.setStatus(Response.Status.UNAUTHORIZED);
|
// This is used for testing only. Selenium will execute the HTML challenge sent back which results in the javascript
|
||||||
loginForm.setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader);
|
// redirecting. Our old Selenium tests expect that the current URL will be the original openid redirect.
|
||||||
return loginForm.createLogin();
|
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>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticatorModel;
|
import org.keycloak.models.AuthenticatorModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -43,6 +45,27 @@ public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return PROVIDER_ID;
|
return PROVIDER_ID;
|
||||||
|
|
|
@ -128,6 +128,8 @@ public class Messages {
|
||||||
|
|
||||||
public static final String COULD_NOT_SEND_AUTHENTICATION_REQUEST = "couldNotSendAuthenticationRequestMessage";
|
public static final String COULD_NOT_SEND_AUTHENTICATION_REQUEST = "couldNotSendAuthenticationRequestMessage";
|
||||||
|
|
||||||
|
public static final String KERBEROS_NOT_ENABLED="kerberosNotSetUp";
|
||||||
|
|
||||||
public static final String UNEXPECTED_ERROR_HANDLING_REQUEST = "unexpectedErrorHandlingRequestMessage";
|
public static final String UNEXPECTED_ERROR_HANDLING_REQUEST = "unexpectedErrorHandlingRequestMessage";
|
||||||
|
|
||||||
public static final String INVALID_ACCESS_CODE = "invalidAccessCodeMessage";
|
public static final String INVALID_ACCESS_CODE = "invalidAccessCodeMessage";
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
|
import org.keycloak.authentication.Authenticator;
|
||||||
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
|
import org.keycloak.authentication.AuthenticatorUtil;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
|
import org.keycloak.models.AuthenticatorModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.PUT;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public class AuthenticationFlowResource {
|
||||||
|
|
||||||
|
private final RealmModel realm;
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private RealmAuth auth;
|
||||||
|
private AdminEventBuilder adminEvent;
|
||||||
|
private static Logger logger = Logger.getLogger(AuthenticationFlowResource.class);
|
||||||
|
|
||||||
|
public AuthenticationFlowResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) {
|
||||||
|
this.realm = realm;
|
||||||
|
this.session = session;
|
||||||
|
this.auth = auth;
|
||||||
|
this.auth.init(RealmAuth.Resource.IDENTITY_PROVIDER);
|
||||||
|
this.adminEvent = adminEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AuthenticationExecutionRepresentation {
|
||||||
|
protected String execution;
|
||||||
|
protected String referenceType;
|
||||||
|
protected String requirement;
|
||||||
|
protected List<String> requirementChoices;
|
||||||
|
protected Boolean configurable;
|
||||||
|
protected Boolean subFlow;
|
||||||
|
|
||||||
|
public String getExecution() {
|
||||||
|
return execution;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExecution(String execution) {
|
||||||
|
this.execution = execution;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReferenceType() {
|
||||||
|
return referenceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReferenceType(String referenceType) {
|
||||||
|
this.referenceType = referenceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequirement() {
|
||||||
|
return requirement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequirement(String requirement) {
|
||||||
|
this.requirement = requirement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getRequirementChoices() {
|
||||||
|
return requirementChoices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequirementChoices(List<String> requirementChoices) {
|
||||||
|
this.requirementChoices = requirementChoices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getConfigurable() {
|
||||||
|
return configurable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfigurable(Boolean configurable) {
|
||||||
|
this.configurable = configurable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getSubFlow() {
|
||||||
|
return subFlow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubFlow(Boolean subFlow) {
|
||||||
|
this.subFlow = subFlow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path("/flow/{flowAlias}/executions")
|
||||||
|
@GET
|
||||||
|
@NoCache
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public Response getExecutions(@PathParam("flowAlias") String flowAlias) {
|
||||||
|
this.auth.requireView();
|
||||||
|
|
||||||
|
AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
|
||||||
|
if (flow == null) {
|
||||||
|
logger.debug("flow not found: " + flowAlias);
|
||||||
|
return Response.status(NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
List<AuthenticationExecutionRepresentation> result = new LinkedList<>();
|
||||||
|
List<AuthenticationExecutionModel> executions = AuthenticatorUtil.getEnabledExecutionsRecursively(realm, flow.getId());
|
||||||
|
for (AuthenticationExecutionModel execution : executions) {
|
||||||
|
AuthenticationExecutionRepresentation rep = new AuthenticationExecutionRepresentation();
|
||||||
|
rep.setSubFlow(false);
|
||||||
|
rep.setRequirementChoices(new LinkedList<String>());
|
||||||
|
if (execution.isAutheticatorFlow()) {
|
||||||
|
AuthenticationFlowModel flowRef = realm.getAuthenticationFlowById(execution.getAuthenticator());
|
||||||
|
rep.setReferenceType(flowRef.getAlias());
|
||||||
|
rep.setExecution(execution.getId());
|
||||||
|
rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.ALTERNATIVE.name());
|
||||||
|
rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.REQUIRED.name());
|
||||||
|
rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.DISABLED.name());
|
||||||
|
rep.setConfigurable(false);
|
||||||
|
rep.setExecution(execution.getId());
|
||||||
|
rep.setRequirement(execution.getRequirement().name());
|
||||||
|
result.add(rep);
|
||||||
|
} else {
|
||||||
|
if (!flow.getId().equals(execution.getParentFlow())) {
|
||||||
|
rep.setSubFlow(true);
|
||||||
|
}
|
||||||
|
AuthenticatorModel authenticator = realm.getAuthenticatorById(execution.getAuthenticator());
|
||||||
|
AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, authenticator.getProviderId());
|
||||||
|
if (factory.getReferenceType() == null) continue;
|
||||||
|
rep.setReferenceType(factory.getReferenceType());
|
||||||
|
rep.setConfigurable(factory.isConfigurable());
|
||||||
|
for (AuthenticationExecutionModel.Requirement choice : factory.getRequirementChoices()) {
|
||||||
|
rep.getRequirementChoices().add(choice.name());
|
||||||
|
}
|
||||||
|
rep.setExecution(execution.getId());
|
||||||
|
rep.setRequirement(execution.getRequirement().name());
|
||||||
|
result.add(rep);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return Response.ok(result).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path("/flow/{flowAlias}/executions")
|
||||||
|
@PUT
|
||||||
|
@NoCache
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
public void updateExecutions(@PathParam("flowAlias") String flowAlias, AuthenticationExecutionRepresentation rep) {
|
||||||
|
this.auth.requireManage();
|
||||||
|
|
||||||
|
AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
|
||||||
|
if (flow == null) {
|
||||||
|
logger.debug("flow not found: " + flowAlias);
|
||||||
|
throw new NotFoundException("flow not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(rep.getExecution());
|
||||||
|
if (model == null) {
|
||||||
|
session.getTransaction().setRollbackOnly();
|
||||||
|
throw new NotFoundException("Illegal execution");
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!model.getRequirement().name().equals(rep.getRequirement())) {
|
||||||
|
model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement()));
|
||||||
|
realm.updateAuthenticatorExecution(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -238,6 +238,15 @@ public class RealmAdminResource {
|
||||||
return fed;
|
return fed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("authentication-flows")
|
||||||
|
public AuthenticationFlowResource flows() {
|
||||||
|
AuthenticationFlowResource resource = new AuthenticationFlowResource(realm, session, auth, adminEvent);
|
||||||
|
ResteasyProviderFactory.getInstance().injectProperties(resource);
|
||||||
|
//resourceContext.initResource(resource);
|
||||||
|
return resource;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path for managing all realm-level or client-level roles defined in this realm by it's id.
|
* Path for managing all realm-level or client-level roles defined in this realm by it's id.
|
||||||
*
|
*
|
||||||
|
|
|
@ -4,4 +4,3 @@ org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory
|
||||||
org.keycloak.authentication.authenticators.LoginFormUsernameAuthenticatorFactory
|
org.keycloak.authentication.authenticators.LoginFormUsernameAuthenticatorFactory
|
||||||
org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory
|
org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory
|
||||||
org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory
|
org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory
|
||||||
org.keycloak.authentication.authenticators.SetRequiredActionAuthenticatorFactory
|
|
|
@ -18,6 +18,7 @@ import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.adapters.HttpClientBuilder;
|
import org.keycloak.adapters.HttpClientBuilder;
|
||||||
|
import org.keycloak.authentication.authenticators.SpnegoAuthenticator;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.federation.kerberos.CommonKerberosConfig;
|
import org.keycloak.federation.kerberos.CommonKerberosConfig;
|
||||||
import org.keycloak.constants.KerberosConstants;
|
import org.keycloak.constants.KerberosConstants;
|
||||||
|
@ -35,6 +36,7 @@ import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.OAuthClient;
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
import org.keycloak.testsuite.pages.AccountPasswordPage;
|
import org.keycloak.testsuite.pages.AccountPasswordPage;
|
||||||
|
import org.keycloak.testsuite.pages.BypassKerberosPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.WebResource;
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
|
@ -59,6 +61,9 @@ public abstract class AbstractKerberosTest {
|
||||||
@WebResource
|
@WebResource
|
||||||
protected LoginPage loginPage;
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected BypassKerberosPage bypassPage;
|
||||||
|
|
||||||
@WebResource
|
@WebResource
|
||||||
protected AccountPasswordPage changePasswordPage;
|
protected AccountPasswordPage changePasswordPage;
|
||||||
|
|
||||||
|
@ -85,6 +90,7 @@ public abstract class AbstractKerberosTest {
|
||||||
public void spnegoNotAvailableTest() throws Exception {
|
public void spnegoNotAvailableTest() throws Exception {
|
||||||
initHttpClient(false);
|
initHttpClient(false);
|
||||||
|
|
||||||
|
SpnegoAuthenticator.bypassChallengeJavascript = true;
|
||||||
driver.navigate().to(KERBEROS_APP_URL);
|
driver.navigate().to(KERBEROS_APP_URL);
|
||||||
String kcLoginPageLocation = driver.getCurrentUrl();
|
String kcLoginPageLocation = driver.getCurrentUrl();
|
||||||
|
|
||||||
|
@ -94,6 +100,7 @@ public abstract class AbstractKerberosTest {
|
||||||
String responseText = response.readEntity(String.class);
|
String responseText = response.readEntity(String.class);
|
||||||
responseText.contains("Log in to test");
|
responseText.contains("Log in to test");
|
||||||
response.close();
|
response.close();
|
||||||
|
SpnegoAuthenticator.bypassChallengeJavascript = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -133,6 +140,10 @@ public abstract class AbstractKerberosTest {
|
||||||
|
|
||||||
// Login with username/password from kerberos
|
// Login with username/password from kerberos
|
||||||
changePasswordPage.open();
|
changePasswordPage.open();
|
||||||
|
// Only needed if you are providing a click thru to bypass kerberos. Currently there is a javascript
|
||||||
|
// to forward the user if kerberos isn't enabled.
|
||||||
|
//bypassPage.isCurrent();
|
||||||
|
//bypassPage.clickContinue();
|
||||||
loginPage.assertCurrent();
|
loginPage.assertCurrent();
|
||||||
loginPage.login("jduke", "theduke");
|
loginPage.login("jduke", "theduke");
|
||||||
changePasswordPage.assertCurrent();
|
changePasswordPage.assertCurrent();
|
||||||
|
@ -149,6 +160,10 @@ public abstract class AbstractKerberosTest {
|
||||||
Assert.assertTrue(driver.getPageSource().contains("Your password has been updated."));
|
Assert.assertTrue(driver.getPageSource().contains("Your password has been updated."));
|
||||||
changePasswordPage.logout();
|
changePasswordPage.logout();
|
||||||
|
|
||||||
|
// Only needed if you are providing a click thru to bypass kerberos. Currently there is a javascript
|
||||||
|
// to forward the user if kerberos isn't enabled.
|
||||||
|
//bypassPage.isCurrent();
|
||||||
|
//bypassPage.clickContinue();
|
||||||
// Login with old password doesn't work, but with new password works
|
// Login with old password doesn't work, but with new password works
|
||||||
loginPage.login("jduke", "theduke");
|
loginPage.login("jduke", "theduke");
|
||||||
loginPage.assertCurrent();
|
loginPage.assertCurrent();
|
||||||
|
@ -221,12 +236,16 @@ public abstract class AbstractKerberosTest {
|
||||||
|
|
||||||
|
|
||||||
protected Response spnegoLogin(String username, String password) {
|
protected Response spnegoLogin(String username, String password) {
|
||||||
|
SpnegoAuthenticator.bypassChallengeJavascript = true;
|
||||||
driver.navigate().to(KERBEROS_APP_URL);
|
driver.navigate().to(KERBEROS_APP_URL);
|
||||||
String kcLoginPageLocation = driver.getCurrentUrl();
|
String kcLoginPageLocation = driver.getCurrentUrl();
|
||||||
|
String location = "http://localhost:8081/auth/realms/test/protocol/openid-connect/auth?response_type=code&client_id=kerberos-app&redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Fkerberos-portal&state=0%2F88a96ddd-84fe-4e77-8a46-02394d7b3a7d&login=true";
|
||||||
// Request for SPNEGO login sent with Resteasy client
|
// Request for SPNEGO login sent with Resteasy client
|
||||||
spnegoSchemeFactory.setCredentials(username, password);
|
spnegoSchemeFactory.setCredentials(username, password);
|
||||||
return client.target(kcLoginPageLocation).request().get();
|
Response response = client.target(kcLoginPageLocation).request().get();
|
||||||
|
SpnegoAuthenticator.bypassChallengeJavascript = false;
|
||||||
|
return response;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ public class KerberosLdapTest extends AbstractKerberosTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
CredentialHelper.setRequiredCredential(CredentialRepresentation.KERBEROS, appRealm);
|
CredentialHelper.setAlternativeCredential(CredentialRepresentation.KERBEROS, appRealm);
|
||||||
URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json");
|
URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json");
|
||||||
keycloakRule.createApplicationDeployment()
|
keycloakRule.createApplicationDeployment()
|
||||||
.name("kerberos-portal").contextPath("/kerberos-portal")
|
.name("kerberos-portal").contextPath("/kerberos-portal")
|
||||||
|
@ -109,6 +109,10 @@ public class KerberosLdapTest extends AbstractKerberosTest {
|
||||||
|
|
||||||
// Login with username/password from kerberos
|
// Login with username/password from kerberos
|
||||||
changePasswordPage.open();
|
changePasswordPage.open();
|
||||||
|
// Only needed if you are providing a click thru to bypass kerberos. Currently there is a javascript
|
||||||
|
// to forward the user if kerberos isn't enabled.
|
||||||
|
//bypassPage.isCurrent();
|
||||||
|
//bypassPage.clickContinue();
|
||||||
loginPage.assertCurrent();
|
loginPage.assertCurrent();
|
||||||
loginPage.login("jduke", "theduke");
|
loginPage.login("jduke", "theduke");
|
||||||
changePasswordPage.assertCurrent();
|
changePasswordPage.assertCurrent();
|
||||||
|
@ -118,6 +122,11 @@ public class KerberosLdapTest extends AbstractKerberosTest {
|
||||||
Assert.assertTrue(driver.getPageSource().contains("Your password has been updated."));
|
Assert.assertTrue(driver.getPageSource().contains("Your password has been updated."));
|
||||||
changePasswordPage.logout();
|
changePasswordPage.logout();
|
||||||
|
|
||||||
|
// Only needed if you are providing a click thru to bypass kerberos. Currently there is a javascript
|
||||||
|
// to forward the user if kerberos isn't enabled.
|
||||||
|
//bypassPage.isCurrent();
|
||||||
|
//bypassPage.clickContinue();
|
||||||
|
|
||||||
// Login with old password doesn't work, but with new password works
|
// Login with old password doesn't work, but with new password works
|
||||||
loginPage.login("jduke", "theduke");
|
loginPage.login("jduke", "theduke");
|
||||||
loginPage.assertCurrent();
|
loginPage.assertCurrent();
|
||||||
|
@ -139,6 +148,11 @@ public class KerberosLdapTest extends AbstractKerberosTest {
|
||||||
|
|
||||||
// Change password back
|
// Change password back
|
||||||
changePasswordPage.open();
|
changePasswordPage.open();
|
||||||
|
// Only needed if you are providing a click thru to bypass kerberos. Currently there is a javascript
|
||||||
|
// to forward the user if kerberos isn't enabled.
|
||||||
|
//bypassPage.isCurrent();
|
||||||
|
//bypassPage.clickContinue();
|
||||||
|
|
||||||
loginPage.login("jduke", "newPass");
|
loginPage.login("jduke", "newPass");
|
||||||
changePasswordPage.assertCurrent();
|
changePasswordPage.assertCurrent();
|
||||||
changePasswordPage.changePassword("newPass", "theduke", "theduke");
|
changePasswordPage.changePassword("newPass", "theduke", "theduke");
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class KerberosStandaloneTest extends AbstractKerberosTest {
|
||||||
@Override
|
@Override
|
||||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
|
||||||
CredentialHelper.setRequiredCredential(CredentialRepresentation.KERBEROS, appRealm);
|
CredentialHelper.setAlternativeCredential(CredentialRepresentation.KERBEROS, appRealm);
|
||||||
URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json");
|
URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json");
|
||||||
keycloakRule.createApplicationDeployment()
|
keycloakRule.createApplicationDeployment()
|
||||||
.name("kerberos-portal").contextPath("/kerberos-portal")
|
.name("kerberos-portal").contextPath("/kerberos-portal")
|
||||||
|
@ -104,6 +104,11 @@ public class KerberosStandaloneTest extends AbstractKerberosTest {
|
||||||
assertUser("hnelson", "hnelson@keycloak.org", null, null, false);
|
assertUser("hnelson", "hnelson@keycloak.org", null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Override
|
||||||
|
public void usernamePasswordLoginTest() throws Exception {
|
||||||
|
super.usernamePasswordLoginTest();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateProfileEnabledTest() throws Exception {
|
public void updateProfileEnabledTest() throws Exception {
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* 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.testsuite.pages;
|
||||||
|
|
||||||
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
|
import org.openqa.selenium.By;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class BypassKerberosPage extends AbstractPage {
|
||||||
|
|
||||||
|
@FindBy(name = "continue")
|
||||||
|
private WebElement continueButton;
|
||||||
|
|
||||||
|
public boolean isCurrent() {
|
||||||
|
return driver.getTitle().equals("Log in to test") || driver.getTitle().equals("Anmeldung bei test");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clickContinue() {
|
||||||
|
continueButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void open() throws Exception {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,7 @@
|
||||||
package org.keycloak.testsuite.utils;
|
package org.keycloak.testsuite.utils;
|
||||||
|
|
||||||
import org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory;
|
import org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory;
|
||||||
import org.keycloak.authentication.authenticators.OTPFormAuthenticator;
|
|
||||||
import org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory;
|
import org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory;
|
||||||
import org.keycloak.authentication.authenticators.SpnegoAuthenticator;
|
|
||||||
import org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory;
|
import org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticationFlowModel;
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
|
@ -19,18 +17,28 @@ import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
public class CredentialHelper {
|
public class CredentialHelper {
|
||||||
|
|
||||||
public static void setRequiredCredential(String type, RealmModel realm) {
|
public static void setRequiredCredential(String type, RealmModel realm) {
|
||||||
|
AuthenticationExecutionModel.Requirement requirement = AuthenticationExecutionModel.Requirement.REQUIRED;
|
||||||
|
setCredentialRequirement(type, realm, requirement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setAlternativeCredential(String type, RealmModel realm) {
|
||||||
|
AuthenticationExecutionModel.Requirement requirement = AuthenticationExecutionModel.Requirement.ALTERNATIVE;
|
||||||
|
setCredentialRequirement(type, realm, requirement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCredentialRequirement(String type, RealmModel realm, AuthenticationExecutionModel.Requirement requirement) {
|
||||||
if (type.equals(CredentialRepresentation.TOTP)) {
|
if (type.equals(CredentialRepresentation.TOTP)) {
|
||||||
String providerId = OTPFormAuthenticatorFactory.PROVIDER_ID;
|
String providerId = OTPFormAuthenticatorFactory.PROVIDER_ID;
|
||||||
String flowAlias = DefaultAuthenticationFlows.FORMS_FLOW;
|
String flowAlias = DefaultAuthenticationFlows.FORMS_FLOW;
|
||||||
requireAuthentication(realm, providerId, flowAlias);
|
authenticationRequirement(realm, providerId, flowAlias, requirement);
|
||||||
} else if (type.equals(CredentialRepresentation.KERBEROS)) {
|
} else if (type.equals(CredentialRepresentation.KERBEROS)) {
|
||||||
String providerId = SpnegoAuthenticatorFactory.PROVIDER_ID;
|
String providerId = SpnegoAuthenticatorFactory.PROVIDER_ID;
|
||||||
String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW;
|
String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW;
|
||||||
alternativeAuthentication(realm, providerId, flowAlias);
|
authenticationRequirement(realm, providerId, flowAlias, requirement);
|
||||||
} else if (type.equals(CredentialRepresentation.PASSWORD)) {
|
} else if (type.equals(CredentialRepresentation.PASSWORD)) {
|
||||||
String providerId = LoginFormPasswordAuthenticatorFactory.PROVIDER_ID;
|
String providerId = LoginFormPasswordAuthenticatorFactory.PROVIDER_ID;
|
||||||
String flowAlias = DefaultAuthenticationFlows.FORMS_FLOW;
|
String flowAlias = DefaultAuthenticationFlows.FORMS_FLOW;
|
||||||
requireAuthentication(realm, providerId, flowAlias);
|
authenticationRequirement(realm, providerId, flowAlias, requirement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,11 +50,6 @@ public class CredentialHelper {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void requireAuthentication(RealmModel realm, String authenticatorProviderId, String flowAlias) {
|
|
||||||
AuthenticationExecutionModel.Requirement requirement = AuthenticationExecutionModel.Requirement.REQUIRED;
|
|
||||||
authenticationRequirement(realm, authenticatorProviderId, flowAlias, requirement);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void alternativeAuthentication(RealmModel realm, String authenticatorProviderId, String flowAlias) {
|
public static void alternativeAuthentication(RealmModel realm, String authenticatorProviderId, String flowAlias) {
|
||||||
AuthenticationExecutionModel.Requirement requirement = AuthenticationExecutionModel.Requirement.ALTERNATIVE;
|
AuthenticationExecutionModel.Requirement requirement = AuthenticationExecutionModel.Requirement.ALTERNATIVE;
|
||||||
authenticationRequirement(realm, authenticatorProviderId, flowAlias, requirement);
|
authenticationRequirement(realm, authenticatorProviderId, flowAlias, requirement);
|
||||||
|
|
Loading…
Reference in a new issue