Skip to content

Commit

Permalink
KEYCLOAK-1795 Add just one clientAuthenticatorType per client
Browse files Browse the repository at this point in the history
  • Loading branch information
mposolda committed Sep 3, 2015
1 parent 25e53db commit bc18955
Show file tree
Hide file tree
Showing 38 changed files with 274 additions and 205 deletions.
Expand Up @@ -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" />
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -89,6 +90,14 @@ public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}

public String getClientAuthenticatorType() {
return clientAuthenticatorType;
}

public void setClientAuthenticatorType(String clientAuthenticatorType) {
this.clientAuthenticatorType = clientAuthenticatorType;
}

public String getSecret() {
return secret;
}
Expand Down
5 changes: 3 additions & 2 deletions docbook/reference/en/en-US/modules/auth-spi.xml
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
Expand Up @@ -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",
Expand Down
13 changes: 13 additions & 0 deletions examples/demo-template/testrealm.json
Expand Up @@ -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" : {
Expand Down Expand Up @@ -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=="
}
Expand Down
Expand Up @@ -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) {
Expand Down Expand Up @@ -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.");
});
};
Expand Down
Expand Up @@ -19,7 +19,7 @@ <h1>Authentication</h1>
<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>
Expand Down
Expand Up @@ -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>
Expand Up @@ -2,6 +2,7 @@

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;
Expand Down Expand Up @@ -38,6 +39,10 @@ public void migrate(KeycloakSession session) {
} else {
realm.setClientAuthenticationFlow(realm.getFlowByAlias(DefaultAuthenticationFlows.CLIENT_AUTHENTICATION_FLOW));
}

for (ClientModel client : realm.getClients()) {
client.setClientAuthenticatorType(KeycloakModelUtils.getDefaultClientAuthenticatorType());
}
}

}
Expand Down
3 changes: 3 additions & 0 deletions model/api/src/main/java/org/keycloak/models/ClientModel.java
Expand Up @@ -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);
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -67,6 +68,14 @@ public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public String getClientAuthenticatorType() {
return clientAuthenticatorType;
}

public void setClientAuthenticatorType(String clientAuthenticatorType) {
this.clientAuthenticatorType = clientAuthenticatorType;
}

public String getSecret() {
return secret;
}
Expand Down
Expand Up @@ -180,12 +180,17 @@ public static UserCredentialModel generateSecret(ClientModel app) {
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);

Expand Down
Expand Up @@ -307,6 +307,7 @@ public static ClientRepresentation toRepresentation(ClientModel clientModel) {
rep.setBaseUrl(clientModel.getBaseUrl());
rep.setNotBefore(clientModel.getNotBefore());
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType());

Set<String> redirectUris = clientModel.getRedirectUris();
if (redirectUris != null) {
Expand Down
Expand Up @@ -692,6 +692,12 @@ public static ClientModel createClient(KeycloakSession session, RealmModel realm
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);
Expand Down Expand Up @@ -770,6 +776,7 @@ public static void updateClient(ClientRepresentation rep, ClientModel resource)
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());
Expand Down
Expand Up @@ -145,6 +145,16 @@ public void setEnabled(boolean enabled) {
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());
Expand Down
Expand Up @@ -95,6 +95,18 @@ public void setEnabled(boolean enabled) {
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());
}
Expand Down
Expand Up @@ -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>();
Expand All @@ -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();
Expand Down Expand Up @@ -112,6 +114,10 @@ public boolean isEnabled() {
return enabled;
}

public String getClientAuthenticatorType() {
return clientAuthenticatorType;
}

public String getSecret() {
return secret;
}
Expand Down
10 changes: 10 additions & 0 deletions model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
Expand Up @@ -151,6 +151,16 @@ public void removeRedirectUri(String redirectUri) {
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();
Expand Down

0 comments on commit bc18955

Please sign in to comment.