Merge pull request #1588 from mposolda/master

KEYCLOAK-1795 Add just one clientAuthenticatorType per client
This commit is contained in:
Marek Posolda 2015-09-04 00:35:25 +02:00
commit e9d1475475
38 changed files with 275 additions and 206 deletions

View file

@ -61,6 +61,12 @@
</column>
</addColumn>
<addColumn tableName="CLIENT">
<column name="CLIENT_AUTHENTICATOR_TYPE" type="VARCHAR(255)">
<constraints nullable="true"/>
</column>
</addColumn>
<!-- Sybase specific hacks -->
<modifySql dbms="sybase">
<regExpReplace replace=".*(SET DEFAULT NULL)" with="SELECT 1" />

View file

@ -15,6 +15,7 @@ public class ClientRepresentation {
protected String baseUrl;
protected Boolean surrogateAuthRequired;
protected Boolean enabled;
protected String clientAuthenticatorType;
protected String secret;
protected String[] defaultRoles;
protected List<String> redirectUris;
@ -89,6 +90,14 @@ public class ClientRepresentation {
this.baseUrl = baseUrl;
}
public String getClientAuthenticatorType() {
return clientAuthenticatorType;
}
public void setClientAuthenticatorType(String clientAuthenticatorType) {
this.clientAuthenticatorType = clientAuthenticatorType;
}
public String getSecret() {
return secret;
}

View file

@ -921,7 +921,7 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
in the location accessible to your client application
</listitem>
<listitem>
Uploaded in Keycloak admin console - This option is useful if you already has existing private key of your client.
Uploaded in Keycloak admin console - This option is useful if you already have existing private key of your client.
In this case, you just need to upload the public key and certificate to the Keycloak server.
</listitem>
</itemizedlist>
@ -993,7 +993,8 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
<para>
Finally you need to configure admin console . You need to create new client authentication flow and define execution
with your authenticator (you can also add the builtin authenticators and configure requirements etc)
and finally configure Clients binding . See <link linkend="adding_authenticator">Adding Authenticator</link> for more details.
and finally configure Clients binding . See <link linkend="adding_authenticator">Adding Authenticator</link> for more details. Then
you need to go to Client credentials tab and choose the method for authentication your client and configure client credentials (if possible).
</para>
</listitem>
</varlistentry>

View file

@ -3,7 +3,7 @@
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "product-sa-client",
"resource" : "product-sa-client-jwt-auth",
"credentials": {
"jwt": {
"client-keystore-file": "classpath:keystore-client.jks",

View file

@ -78,6 +78,13 @@
"email" : "service-account-product-sa-client@placeholder.org",
"serviceAccountClientId": "product-sa-client",
"realmRoles": [ "user" ]
},
{
"username" : "service-account-product-sa-client-jwt-auth",
"enabled": true,
"email" : "service-account-product-sa-client-jwt-auth@placeholder.org",
"serviceAccountClientId": "product-sa-client-jwt-auth",
"realmRoles": [ "user" ]
}
],
"roles" : {
@ -173,7 +180,13 @@
"clientId": "product-sa-client",
"enabled": true,
"secret": "password",
"serviceAccountsEnabled": true
},
{
"clientId": "product-sa-client-jwt-auth",
"enabled": true,
"serviceAccountsEnabled": true,
"clientAuthenticatorType": "client-jwt",
"attributes": {
"jwt.credential.certificate": "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ=="
}

View file

@ -30,10 +30,42 @@ module.controller('ClientRoleListCtrl', function($scope, $location, realm, clien
});
});
module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, Notifications) {
module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, Client) {
$scope.realm = realm;
$scope.client = client;
$scope.client = angular.copy(client);
$scope.clientAuthenticatorProviders = clientAuthenticatorProviders;
var updateConfigButtonVisibility = function() {
for (var i=0 ; i<clientAuthenticatorProviders.length ; i++) {
var authenticator = clientAuthenticatorProviders[i];
if ($scope.client.clientAuthenticatorType === authenticator.id) {
$scope.configButtonVisible = authenticator.configurablePerClient;
}
}
};
updateConfigButtonVisibility();
$scope.$watch('client', function() {
if (!angular.equals($scope.client, client)) {
console.log("Update client credentials!");
Client.update({
realm : realm.realm,
client : client.id
}, $scope.client, function() {
$scope.changed = false;
client = angular.copy($scope.client);
updateConfigButtonVisibility();
});
}
}, true);
$scope.configureAuthenticator = function() {
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/" + client.clientAuthenticatorType);
}
});
module.controller('ClientSecretCtrl', function($scope, $location, realm, client, ClientSecret, Notifications) {
@ -115,7 +147,7 @@ module.controller('ClientGenericCredentialsCtrl', function($scope, $location, re
client : client.id
}, $scope.client, function() {
$scope.changed = false;
client = $scope.client;
client = angular.copy($scope.client);
Notifications.success("Client authentication configuration has been saved to the client.");
});
};

View file

@ -19,7 +19,7 @@
<button class="btn btn-default" data-ng-click="copyFlow()">Copy</button>
<button class="btn btn-default" data-ng-hide="flow.builtIn" data-ng-click="removeFlow()">Delete</button>
<button class="btn btn-default" data-ng-hide="flow.builtIn" data-ng-click="addExecution()">Add Execution</button>
<button class="btn btn-default" data-ng-hide="flow.builtIn" data-ng-click="addFlow()">Add Flow</button>
<button class="btn btn-default" data-ng-hide="flow.builtIn || flow.providerId === 'client-flow'" data-ng-click="addFlow()">Add Flow</button>
</div>
</th>
</tr>

View file

@ -7,24 +7,27 @@
<kc-tabs-client></kc-tabs-client>
<table class="table table-striped table-bordered">
<thead>
<tr data-ng-hide="executions.length == 0">
<th>Client Auth Type</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="authenticator in clientAuthenticatorProviders" data-ng-show="clientAuthenticatorProviders.length > 0">
<td ng-repeat="lev in execution.preLevels"></td>
<td>{{authenticator.displayName|capitalize}}</td>
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials/{{authenticator.id}}" data-ng-show="authenticator.configurablePerClient">Configure</a></td>
</tr>
<tr data-ng-show="clientAuthenticatorProviders.length == 0">
<td>No client authenticators available</td>
</tr>
</tbody>
</table>
<form class="form-horizontal" name="clientForm" novalidate kc-read-only="!access.manageClients">
<fieldset class="border-top">
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="clientAuthenticatorType"> Client Authenticator</label>
<div class="col-md-2">
<div>
<select class="form-control" id="clientAuthenticatorType"
ng-model="client.clientAuthenticatorType"
ng-options="authenticator.id as authenticator.displayName for authenticator in clientAuthenticatorProviders"
required>
</select>
</div>
</div>
<kc-tooltip>Client Authenticator used for authentication this client against Keycloak server</kc-tooltip>
<div class="col-sm-4" data-ng-show="access.manageRealm">
<a class="btn btn-primary" data-ng-show="configButtonVisible" data-ng-click="configureAuthenticator()">Configure chosen authenticator</a>
</div>
</div>
</fieldset>
</form>
</div>
<kc-menu></kc-menu>

View file

@ -2,6 +2,7 @@ package org.keycloak.migration.migrators;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ImpersonationConstants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OTPPolicy;
@ -38,6 +39,10 @@ public class MigrateTo1_5_0 {
} else {
realm.setClientAuthenticationFlow(realm.getFlowByAlias(DefaultAuthenticationFlows.CLIENT_AUTHENTICATION_FLOW));
}
for (ClientModel client : realm.getClients()) {
client.setClientAuthenticatorType(KeycloakModelUtils.getDefaultClientAuthenticatorType());
}
}
}

View file

@ -75,6 +75,9 @@ public interface ClientModel extends RoleContainerModel {
void setNodeReRegistrationTimeout(int timeout);
String getClientAuthenticatorType();
void setClientAuthenticatorType(String clientAuthenticatorType);
boolean validateSecret(String secret);
String getSecret();
public void setSecret(String secret);

View file

@ -14,6 +14,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
private String name;
private String realmId;
private boolean enabled;
private String clientAuthenticatorType;
private String secret;
private String protocol;
private int notBefore;
@ -67,6 +68,14 @@ public class ClientEntity extends AbstractIdentifiableEntity {
this.enabled = enabled;
}
public String getClientAuthenticatorType() {
return clientAuthenticatorType;
}
public void setClientAuthenticatorType(String clientAuthenticatorType) {
this.clientAuthenticatorType = clientAuthenticatorType;
}
public String getSecret() {
return secret;
}

View file

@ -180,12 +180,17 @@ public final class KeycloakModelUtils {
return secret;
}
public static String getDefaultClientAuthenticatorType() {
return "client-secret";
}
public static String generateCodeSecret() {
return UUID.randomUUID().toString();
}
public static ClientModel createClient(RealmModel realm, String name) {
ClientModel app = realm.addClient(name);
app.setClientAuthenticatorType(getDefaultClientAuthenticatorType());
generateSecret(app);
app.setFullScopeAllowed(true);

View file

@ -307,6 +307,7 @@ public class ModelToRepresentation {
rep.setBaseUrl(clientModel.getBaseUrl());
rep.setNotBefore(clientModel.getNotBefore());
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType());
Set<String> redirectUris = clientModel.getRedirectUris();
if (redirectUris != null) {

View file

@ -692,6 +692,12 @@ public class RepresentationToModel {
client.setNotBefore(resourceRep.getNotBefore());
}
if (resourceRep.getClientAuthenticatorType() != null) {
client.setClientAuthenticatorType(resourceRep.getClientAuthenticatorType());
} else {
client.setClientAuthenticatorType(KeycloakModelUtils.getDefaultClientAuthenticatorType());
}
client.setSecret(resourceRep.getSecret());
if (client.getSecret() == null) {
KeycloakModelUtils.generateSecret(client);
@ -770,6 +776,7 @@ public class RepresentationToModel {
if (rep.getBaseUrl() != null) resource.setBaseUrl(rep.getBaseUrl());
if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
if (rep.getNodeReRegistrationTimeout() != null) resource.setNodeReRegistrationTimeout(rep.getNodeReRegistrationTimeout());
if (rep.getClientAuthenticatorType() != null) resource.setClientAuthenticatorType(rep.getClientAuthenticatorType());
resource.updateClient();
if (rep.getProtocol() != null) resource.setProtocol(rep.getProtocol());

View file

@ -145,6 +145,16 @@ public class ClientAdapter implements ClientModel {
entity.setEnabled(enabled);
}
@Override
public String getClientAuthenticatorType() {
return entity.getClientAuthenticatorType();
}
@Override
public void setClientAuthenticatorType(String clientAuthenticatorType) {
entity.setClientAuthenticatorType(clientAuthenticatorType);
}
@Override
public boolean validateSecret(String secret) {
return secret.equals(entity.getSecret());

View file

@ -95,6 +95,18 @@ public class ClientAdapter implements ClientModel {
updated.setEnabled(enabled);
}
@Override
public String getClientAuthenticatorType() {
if (updated != null) return updated.getClientAuthenticatorType();
return cached.getClientAuthenticatorType();
}
@Override
public void setClientAuthenticatorType(String clientAuthenticatorType) {
getDelegateForUpdate();
updated.setClientAuthenticatorType(clientAuthenticatorType);
}
public boolean validateSecret(String secret) {
return secret.equals(getSecret());
}

View file

@ -29,6 +29,7 @@ public class CachedClient implements Serializable {
private String realm;
private Set<String> redirectUris = new HashSet<String>();
private boolean enabled;
private String clientAuthenticatorType;
private String secret;
private String protocol;
private Map<String, String> attributes = new HashMap<String, String>();
@ -53,6 +54,7 @@ public class CachedClient implements Serializable {
public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
id = model.getId();
clientAuthenticatorType = model.getClientAuthenticatorType();
secret = model.getSecret();
clientId = model.getClientId();
name = model.getName();
@ -112,6 +114,10 @@ public class CachedClient implements Serializable {
return enabled;
}
public String getClientAuthenticatorType() {
return clientAuthenticatorType;
}
public String getSecret() {
return secret;
}

View file

@ -151,6 +151,16 @@ public class ClientAdapter implements ClientModel {
entity.getRedirectUris().remove(redirectUri);
}
@Override
public String getClientAuthenticatorType() {
return entity.getClientAuthenticatorType();
}
@Override
public void setClientAuthenticatorType(String clientAuthenticatorType) {
entity.setClientAuthenticatorType(clientAuthenticatorType);
}
@Override
public String getSecret() {
return entity.getSecret();

View file

@ -40,6 +40,8 @@ public class ClientEntity {
private boolean enabled;
@Column(name="SECRET")
private String secret;
@Column(name="CLIENT_AUTHENTICATOR_TYPE")
private String clientAuthenticatorType;
@Column(name="NOT_BEFORE")
private int notBefore;
@Column(name="PUBLIC_CLIENT")
@ -170,6 +172,14 @@ public class ClientEntity {
this.redirectUris = redirectUris;
}
public String getClientAuthenticatorType() {
return clientAuthenticatorType;
}
public void setClientAuthenticatorType(String clientAuthenticatorType) {
this.clientAuthenticatorType = clientAuthenticatorType;
}
public String getSecret() {
return secret;
}

View file

@ -141,6 +141,17 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
updateMongoEntity();
}
@Override
public String getClientAuthenticatorType() {
return getMongoEntity().getClientAuthenticatorType();
}
@Override
public void setClientAuthenticatorType(String clientAuthenticatorType) {
getMongoEntity().setClientAuthenticatorType(clientAuthenticatorType);
updateMongoEntity();
}
@Override
public boolean validateSecret(String secret) {
return secret.equals(getMongoEntity().getSecret());

View file

@ -1,5 +1,7 @@
package org.keycloak.authentication;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@ -11,6 +13,7 @@ import org.keycloak.events.Errors;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -18,19 +21,12 @@ import org.keycloak.models.ClientModel;
public class ClientAuthenticationFlow implements AuthenticationFlow {
Response alternativeChallenge = null;
boolean alternativeSuccessful = false;
List<AuthenticationExecutionModel> executions;
Iterator<AuthenticationExecutionModel> executionIterator;
AuthenticationProcessor processor;
AuthenticationFlowModel flow;
private List<String> successAuthenticators = new LinkedList<>();
public ClientAuthenticationFlow(AuthenticationProcessor processor, AuthenticationFlowModel flow) {
this.processor = processor;
this.flow = flow;
this.executions = processor.getRealm().getAuthenticationExecutions(flow.getId());
this.executionIterator = executions.iterator();
}
@Override
@ -40,131 +36,109 @@ public class ClientAuthenticationFlow implements AuthenticationFlow {
@Override
public Response processFlow() {
while (executionIterator.hasNext()) {
AuthenticationExecutionModel model = executionIterator.next();
if (model.isDisabled()) {
continue;
}
if (model.isAlternative() && alternativeSuccessful) {
continue;
}
if (model.isAuthenticatorFlow()) {
AuthenticationFlow authenticationFlow;
authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
Response flowChallenge = authenticationFlow.processFlow();
if (flowChallenge == null) {
if (model.isAlternative()) alternativeSuccessful = true;
continue;
} else {
if (model.isAlternative()) {
alternativeChallenge = flowChallenge;
} else if (model.isRequired()) {
return flowChallenge;
} else {
continue;
}
return flowChallenge;
}
}
List<AuthenticationExecutionModel> executions = findExecutionsToRun();
for (AuthenticationExecutionModel model : executions) {
ClientAuthenticatorFactory factory = (ClientAuthenticatorFactory) processor.getSession().getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, model.getAuthenticator());
if (factory == null) {
throw new AuthenticationFlowException("Could not find ClientAuthenticatorFactory for: " + model.getAuthenticator(), AuthenticationFlowError.INTERNAL_ERROR);
}
ClientAuthenticator authenticator = factory.create();
AuthenticationProcessor.logger.debugv("client authenticator: {0}", factory.getId());
ClientModel authClient = processor.getClient();
if (authenticator.requiresClient() && authClient == null) {
// Continue if it's alternative or optional flow
if (model.isAlternative() || model.isOptional()) {
AuthenticationProcessor.logger.debugv("client authenticator: {0} requires client, but client not available. Skipping", factory.getId());
continue;
}
if (alternativeChallenge != null) {
return alternativeChallenge;
}
throw new AuthenticationFlowException("client authenticator: " + factory.getId(), AuthenticationFlowError.CLIENT_NOT_FOUND);
}
if (authenticator.requiresClient() && authClient != null) {
boolean configuredFor = authenticator.configuredFor(processor.getSession(), processor.getRealm(), authClient);
if (!configuredFor) {
if (model.isRequired()) {
throw new AuthenticationFlowException("Client setup required for authenticator " + factory.getId() + " for client " + authClient.getClientId(),
AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED);
} else if (model.isOptional()) {
continue;
}
}
}
AuthenticationProcessor.Result context = processor.createClientAuthenticatorContext(model, authenticator, executions);
authenticator.authenticateClient(context);
Response response = processResult(context);
if (response != null) return response;
authClient = processor.getClient();
if (authClient != null && authClient.isPublicClient()) {
AuthenticationProcessor.logger.debugv("Public client {0} identified by {1} . Skip next client authenticators", authClient.getClientId(), factory.getId());
logSuccessEvent();
return null;
ClientModel client = processor.getClient();
if (client != null) {
String expectedClientAuthType = client.getClientAuthenticatorType();
// Fallback to secret just in case (for backwards compatibility)
if (expectedClientAuthType == null) {
expectedClientAuthType = KeycloakModelUtils.getDefaultClientAuthenticatorType();
AuthenticationProcessor.logger.warnv("Client {0} doesn't have have authentication method configured. Fallback to {1}", client.getClientId(), expectedClientAuthType);
}
// Check if client authentication matches
if (factory.getId().equals(expectedClientAuthType)) {
AuthenticationProcessor.logger.debugv("Client {0} authenticated by {1}", client.getClientId(), factory.getId());
processor.getEvent().detail(Details.CLIENT_AUTH_METHOD, factory.getId());
return null;
} else {
throw new AuthenticationFlowException("Client " + client.getClientId() + " was authenticated by incorrect method " + factory.getId(),
AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS);
}
}
}
return finishClientAuthentication();
// Check if any alternative challenge was identified
if (alternativeChallenge != null) {
processor.getEvent().error(Errors.INVALID_CLIENT);
return alternativeChallenge;
}
throw new AuthenticationFlowException("Client was not identified by any client authenticator", AuthenticationFlowError.UNKNOWN_CLIENT);
}
protected List<AuthenticationExecutionModel> findExecutionsToRun() {
List<AuthenticationExecutionModel> executions = processor.getRealm().getAuthenticationExecutions(flow.getId());
List<AuthenticationExecutionModel> executionsToRun = new ArrayList<>();
public Response processResult(AuthenticationProcessor.Result result) {
for (AuthenticationExecutionModel execution : executions) {
if (execution.isRequired()) {
executionsToRun = Arrays.asList(execution);
break;
}
if (execution.isAlternative()) {
executionsToRun.add(execution);
}
}
if (AuthenticationProcessor.logger.isTraceEnabled()) {
List<String> exIds = new ArrayList<>();
for (AuthenticationExecutionModel execution : executionsToRun) {
exIds.add(execution.getId());
}
AuthenticationProcessor.logger.tracef("Using executions for client authentication: %s", exIds.toString());
}
return executionsToRun;
}
protected Response processResult(AuthenticationProcessor.Result result) {
AuthenticationExecutionModel execution = result.getExecution();
FlowStatus status = result.getStatus();
AuthenticationProcessor.logger.debugv("client authenticator {0}: {1}", status.toString(), execution.getAuthenticator());
if (status == FlowStatus.SUCCESS) {
AuthenticationProcessor.logger.debugv("client authenticator SUCCESS: {0}", execution.getAuthenticator());
if (execution.isAlternative()) alternativeSuccessful = true;
successAuthenticators.add(execution.getAuthenticator());
return null;
} else if (status == FlowStatus.FAILED) {
AuthenticationProcessor.logger.debugv("client authenticator FAILED: {0}", execution.getAuthenticator());
if (result.getChallenge() != null) {
return sendChallenge(result, execution);
} else {
throw new AuthenticationFlowException(result.getError());
}
throw new AuthenticationFlowException(result.getError());
} else if (status == FlowStatus.FORCE_CHALLENGE) {
return sendChallenge(result, execution);
} else if (status == FlowStatus.CHALLENGE) {
AuthenticationProcessor.logger.debugv("client authenticator CHALLENGE: {0}", execution.getAuthenticator());
if (execution.isRequired()) {
return sendChallenge(result, execution);
}
ClientModel client = processor.getClient();
if (execution.isOptional() && client != null && result.getClientAuthenticator().configuredFor(processor.getSession(), processor.getRealm(), client)) {
return sendChallenge(result, execution);
}
// Make sure the first priority alternative challenge is used
if (execution.isAlternative() && alternativeChallenge == null) {
if (alternativeChallenge == null) {
alternativeChallenge = result.getChallenge();
}
return null;
} else if (status == FlowStatus.FAILURE_CHALLENGE) {
AuthenticationProcessor.logger.debugv("client authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
return sendChallenge(result, execution);
} else if (status == FlowStatus.ATTEMPTED) {
AuthenticationProcessor.logger.debugv("client authenticator ATTEMPTED: {0}", execution.getAuthenticator());
if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS);
}
return null;
} else {
AuthenticationProcessor.logger.debugv("client authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
AuthenticationProcessor.logger.error("Unknown result status");
throw new AuthenticationFlowException(AuthenticationFlowError.INTERNAL_ERROR);
}
}
public Response sendChallenge(AuthenticationProcessor.Result result, AuthenticationExecutionModel execution) {
@ -183,34 +157,4 @@ public class ClientAuthenticationFlow implements AuthenticationFlow {
return result.getChallenge();
}
private Response finishClientAuthentication() {
if (processor.getClient() == null) {
// Check if any alternative challenge was identified
if (alternativeChallenge != null) {
processor.getEvent().error(Errors.INVALID_CLIENT);
return alternativeChallenge;
}
throw new AuthenticationFlowException("Client was not identified by any client authenticator", AuthenticationFlowError.UNKNOWN_CLIENT);
}
logSuccessEvent();
return null;
}
private void logSuccessEvent() {
StringBuilder result = new StringBuilder();
boolean first = true;
for (String authenticator : successAuthenticators) {
if (first) {
first = false;
} else {
result.append(" ");
}
result.append(authenticator);
}
processor.getEvent().detail(Details.CLIENT_AUTH_METHOD, result.toString());
}
}

View file

@ -28,21 +28,4 @@ public interface ClientAuthenticator extends Provider {
*/
void authenticateClient(ClientAuthenticationFlowContext context);
/**
* Does this authenticator require that the client has already been identified? That ClientAuthenticationFlowContext.getClient() is not null?
*
* @return
*/
boolean requiresClient();
/**
* Is this authenticator configured for this client?
*
* @param session
* @param realm
* @param client
* @return
*/
boolean configuredFor(KeycloakSession session, RealmModel realm, ClientModel client);
}

View file

@ -36,7 +36,6 @@ public class ClientIdAndSecretAuthenticator extends AbstractClientAuthenticator
public static final String PROVIDER_ID = "client-secret";
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
AuthenticationExecutionModel.Requirement.DISABLED
};
@ -133,16 +132,6 @@ public class ClientIdAndSecretAuthenticator extends AbstractClientAuthenticator
return true;
}
@Override
public boolean requiresClient() {
return false;
}
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, ClientModel client) {
return client.getSecret() != null;
}
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;

View file

@ -41,7 +41,6 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator {
public static final String CERTIFICATE_ATTR = "jwt.credential.certificate";
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
AuthenticationExecutionModel.Requirement.DISABLED
};
@ -135,16 +134,6 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator {
}
}
@Override
public boolean requiresClient() {
return false;
}
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, ClientModel client) {
return client.getAttribute(CERTIFICATE_ATTR) != null;
}
@Override
public String getDisplayType() {
return "Signed Jwt";

View file

@ -39,7 +39,7 @@ public class AuthorizeClientUtil {
ClientModel client = processor.getClient();
if (client == null) {
throw new ErrorResponseException("invalid_client", "Client authentication was successful, but client is null", Response.Status.BAD_REQUEST);
throw new ErrorResponseException("invalid_client", "Client authentication ended, but client is null", Response.Status.BAD_REQUEST);
}
return new ClientAuthResult(client, processor.getClientAuthAttributes());

View file

@ -133,6 +133,9 @@ public class AdminAPITest {
ClientRepresentation newApp = new ClientRepresentation();
if (appRep.getId() != null) newApp.setId(appRep.getId());
newApp.setClientId(appRep.getClientId());
if (appRep.getClientAuthenticatorType() != null) {
newApp.setClientAuthenticatorType(appRep.getClientAuthenticatorType());
}
if (appRep.getSecret() != null) {
newApp.setSecret(appRep.getSecret());
}
@ -177,6 +180,7 @@ public class AdminAPITest {
if (appRep.getAdminUrl() != null) Assert.assertEquals(appRep.getAdminUrl(), storedApp.getAdminUrl());
if (appRep.getBaseUrl() != null) Assert.assertEquals(appRep.getBaseUrl(), storedApp.getBaseUrl());
if (appRep.isSurrogateAuthRequired() != null) Assert.assertEquals(appRep.isSurrogateAuthRequired(), storedApp.isSurrogateAuthRequired());
if (appRep.getClientAuthenticatorType() != null) Assert.assertEquals(appRep.getClientAuthenticatorType(), storedApp.getClientAuthenticatorType());
if (appRep.getNotBefore() != null) {
Assert.assertEquals(appRep.getNotBefore(), storedApp.getNotBefore());

View file

@ -30,6 +30,7 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.adapter.AdapterTest;
@ -78,7 +79,7 @@ public class LDAPMultipleAttributesTest {
ldapFedProvider.getLdapIdentityStore().updatePassword(bruce, "password");
// Create ldap-portal client
ClientModel ldapClient = appRealm.addClient("ldap-portal");
ClientModel ldapClient = new ClientManager(manager).createClient(appRealm, "ldap-portal");
ldapClient.addRedirectUri("/ldap-portal");
ldapClient.addRedirectUri("/ldap-portal/*");
ldapClient.setManagementUrl("/ldap-portal");

View file

@ -35,6 +35,7 @@ import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.ClientModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
@ -120,8 +121,6 @@ public class CustomFlowTest {
execution.setAuthenticatorFlow(false);
appRealm.addAuthenticatorExecution(execution);
new ClientManager().createClient(appRealm, "dummy-client");
AuthenticationFlowModel clientFlow = new AuthenticationFlowModel();
clientFlow.setAlias("client-dummy");
clientFlow.setDescription("dummy pass through flow");
@ -138,6 +137,11 @@ public class CustomFlowTest {
execution.setPriority(10);
execution.setAuthenticatorFlow(false);
appRealm.addAuthenticatorExecution(execution);
// Set passthrough clientAuthenticator for our clients
ClientModel dummyClient = new ClientManager().createClient(appRealm, "dummy-client");
dummyClient.setClientAuthenticatorType(PassThroughClientAuthenticator.PROVIDER_ID);
appRealm.getClientByClientId("test-app").setClientAuthenticatorType(PassThroughClientAuthenticator.PROVIDER_ID);
}
});

View file

@ -58,16 +58,6 @@ public class PassThroughClientAuthenticator extends AbstractClientAuthenticator
context.success();
}
@Override
public boolean requiresClient() {
return false;
}
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, ClientModel client) {
return true;
}
@Override
public String getDisplayType() {
return "PassThrough Client Validation";

View file

@ -21,6 +21,7 @@ import org.junit.rules.ExternalResource;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.Constants;
import org.keycloak.testsuite.rule.KeycloakRule;
@ -42,7 +43,7 @@ public class JaxrsBasicAuthTest {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ClientModel app = appRealm.addClient("jaxrs-app");
ClientModel app = new ClientManager(manager).createClient(appRealm, "jaxrs-app");
app.setEnabled(true);
app.setSecret("password");
app.setFullScopeAllowed(true);

View file

@ -110,6 +110,10 @@ public class ImportTest extends AbstractModelTest {
Assert.assertTrue(10 == appRegisteredNodes.get("node1"));
Assert.assertTrue(20 == appRegisteredNodes.get("172.10.15.20"));
// test clientAuthenticatorType
Assert.assertEquals(application.getClientAuthenticatorType(), "client-secret");
Assert.assertEquals(otherApp.getClientAuthenticatorType(), "client-jwt");
// Test finding applications by ID
Assert.assertNull(realm.getClientById("982734"));
Assert.assertEquals(application, realm.getClientById(application.getId()));

View file

@ -58,9 +58,11 @@ public class ClientAuthSignedJWTTest {
ClientModel app1 = appRealm.addClient("client1");
new ClientManager(manager).enableServiceAccount(app1);
app1.setAttribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ==");
app1.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
ClientModel app2 = appRealm.addClient("client2");
new ClientManager(manager).enableServiceAccount(app2);
app2.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
// This one is for keystore-client2.p12 , which doesn't work on Sun JDK
// app2.setAttribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFPPLGHHjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjMzMVoXDTI1MDgxNzE3MjUxMVowEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIsatXj38fFD9fHslNrsWrubobudXYwwdZpGYqkHIhuDeSojGvhBSLmKIFmtbHMVcLEbS0dIEsSbNVrwjdFfuRuvd9Vu6Ng0JUC8fRhSeQniC3jcBuP8P4WlXK4+ir3Wlya+T6Hum9b68BiH0KyNZtFGJ6zLHuCcq9Bl0JifvibnUkDeTZPwgJNA9+GxS/x8fAkApcAbJrgBZvr57PwhbgHoZdB8aAY5f5ogbGzKDtSUMvFh+Jah39gWtn7p3VOuuMXA8SugogoH8C5m2itrPBL1UPhAcKUeWiqx4SmZe/lZo7x2WbSecNiFaiqBhIW+QbqCYW6I4u0YvuLuEe3+TC8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZzW5DZviCxUQdV5Ab07PZkUfvImHZ73oWWHZqzUQtZtbVdzfp3cnbb2wyXtlOvingO3hgpoTxV8vbKgLbIQfvkGGHBG1F5e0QVdtikfdcwWb7cy4/9F80OD7cgG0ZAzFbQ8ZY7iS3PToBp3+4tbIK2NK0ntt/MYgJnPbHeG4V4qfgUbFm1YgEK7WpbSVU8jGuJ5DWE+mlYgECZKZ5TSlaVGs2XOm6WXrJScucNekwcBWWiHyRsFHZEDzWmzt8TLTLnnb0vVjhx3qCYxah3RbyyMZm6WLZlLAaGEcwNDO8jaA3hAjrxoOA1xEaolQfGVsb/ElelHcR1Zfe0u4Ekd4tw==");
@ -176,7 +178,7 @@ public class ClientAuthSignedJWTTest {
}
@Test
public void testDirectGrantRequest() throws Exception {
public void testDirectGrantRequestSuccess() throws Exception {
oauth.clientId("client2");
OAuthClient.AccessTokenResponse response = doGrantAccessTokenRequest("test-user@localhost", "password", getClient2SignedJWT());

View file

@ -29,6 +29,7 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.ErrorPage;
@ -49,18 +50,18 @@ public class OAuthRedirectUriTest {
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ClientModel installedApp = appRealm.addClient("test-installed");
ClientModel installedApp = new ClientManager(manager).createClient(appRealm, "test-installed");
installedApp.setEnabled(true);
installedApp.addRedirectUri(Constants.INSTALLED_APP_URN);
installedApp.addRedirectUri(Constants.INSTALLED_APP_URL);
installedApp.setSecret("password");
ClientModel installedApp2 = appRealm.addClient("test-installed2");
ClientModel installedApp2 = new ClientManager(manager).createClient(appRealm, "test-installed2");
installedApp2.setEnabled(true);
installedApp2.addRedirectUri(Constants.INSTALLED_APP_URL + "/myapp");
installedApp2.setSecret("password");
ClientModel installedApp3 = appRealm.addClient("test-wildcard");
ClientModel installedApp3 = new ClientManager(manager).createClient(appRealm, "test-wildcard");
installedApp3.setEnabled(true);
installedApp3.addRedirectUri("http://example.com/foo/*");
installedApp3.addRedirectUri("http://localhost:8081/foo/*");

View file

@ -17,6 +17,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient;
@ -36,7 +37,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ClientModel app = appRealm.addClient("resource-owner");
ClientModel app = new ClientManager(manager).createClient(appRealm, "resource-owner");
app.setSecret("secret");
UserModel user = session.users().addUser(appRealm, "direct-login");

View file

@ -34,11 +34,11 @@ public class ServiceAccountTest {
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ClientModel app = appRealm.addClient("service-account-cl");
ClientModel app = new ClientManager(manager).createClient(appRealm, "service-account-cl");
app.setSecret("secret1");
new ClientManager(manager).enableServiceAccount(app);
ClientModel disabledApp = appRealm.addClient("service-account-disabled");
ClientModel disabledApp = new ClientManager(manager).createClient(appRealm, "service-account-disabled");
disabledApp.setSecret("secret1");
UserModel serviceAccountUser = session.users().getUserByUsername(ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + "service-account-cl", appRealm);

View file

@ -123,6 +123,7 @@
"enabled": true,
"adminUrl": "http://localhost:8081/secure-portal",
"baseUrl": "http://localhost:8081/secure-portal",
"clientAuthenticatorType": "client-jwt",
"redirectUris": [
"http://localhost:8081/secure-portal/*"
],

View file

@ -164,6 +164,7 @@
"name": "Other Application",
"enabled": true,
"serviceAccountsEnabled": true,
"clientAuthenticatorType": "client-jwt",
"protocolMappers" : [
{
"name" : "gss delegation credential",

View file

@ -123,6 +123,7 @@
"enabled": true,
"adminUrl": "http://localhost:8082/secure-portal",
"baseUrl": "http://localhost:8082/secure-portal",
"clientAuthenticatorType": "client-jwt",
"redirectUris": [
"http://localhost:8082/secure-portal/*"
],