Skip to content

Commit

Permalink
KEYCLOAK-2061 Add switches to enable/disable grant types for clients
Browse files Browse the repository at this point in the history
  • Loading branch information
mposolda committed Nov 27, 2015
1 parent ae83ad2 commit 8d2e4c0
Show file tree
Hide file tree
Showing 24 changed files with 348 additions and 70 deletions.
Expand Up @@ -69,11 +69,37 @@
<addUniqueConstraint columnNames="GROUP_ID" constraintName="CON_GROUP_ID_DEF_GROUPS" tableName="REALM_DEFAULT_GROUPS"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>

<addColumn tableName="CLIENT">
<column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
<column name="STANDARD_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
<column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
</addColumn>

<update tableName="CLIENT">
<column name="STANDARD_FLOW_ENABLED" valueBoolean="false"/>
<where>DIRECT_GRANTS_ONLY = :value</where>
<whereParams>
<param valueBoolean="true" />
</whereParams>
</update>

<dropDefaultValue tableName="CLIENT" columnName="DIRECT_GRANTS_ONLY" />
<dropColumn tableName="CLIENT" columnName="DIRECT_GRANTS_ONLY"/>

<modifyDataType tableName="REALM" columnName="PASSWORD_POLICY" newDataType="VARCHAR(2550)"/>

<!-- Sybase specific hacks -->
<modifySql dbms="sybase">
<regExpReplace replace=".*(SET DEFAULT NULL)" with="SELECT 1" />
</modifySql>

</changeSet>
</databaseChangeLog>
Expand Up @@ -28,7 +28,8 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
Update1_2_0_Beta1.class,
Update1_2_0_CR1.class,
Update1_3_0.class,
Update1_4_0.class
Update1_4_0.class,
Update1_7_0.class
};

@Override
Expand Down
@@ -0,0 +1,39 @@
package org.keycloak.connections.mongo.updater.impl.updates;

import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import org.keycloak.models.KeycloakSession;

/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class Update1_7_0 extends Update {

@Override
public String getId() {
return "1.7.0";
}

@Override
public void update(KeycloakSession session) throws ClassNotFoundException {
DBCollection clients = db.getCollection("clients");
DBCursor clientsCursor = clients.find();

try {
while (clientsCursor.hasNext()) {
BasicDBObject client = (BasicDBObject) clientsCursor.next();

boolean directGrantsOnly = client.getBoolean("directGrantsOnly", false);
client.append("standardFlowEnabled", !directGrantsOnly);
client.append("implicitFlowEnabled", false);
client.append("directAccessGrantsEnabled", true);
client.removeField("directGrantsOnly");

clients.save(client);
}
} finally {
clientsCursor.close();
}
}
}
Expand Up @@ -26,6 +26,9 @@ public class ClientRepresentation {
protected Integer notBefore;
protected Boolean bearerOnly;
protected Boolean consentRequired;
protected Boolean standardFlowEnabled;
protected Boolean implicitFlowEnabled;
protected Boolean directAccessGrantsEnabled;
protected Boolean serviceAccountsEnabled;
protected Boolean directGrantsOnly;
protected Boolean publicClient;
Expand Down Expand Up @@ -181,6 +184,30 @@ public void setConsentRequired(Boolean consentRequired) {
this.consentRequired = consentRequired;
}

public Boolean isStandardFlowEnabled() {
return standardFlowEnabled;
}

public void setStandardFlowEnabled(Boolean standardFlowEnabled) {
this.standardFlowEnabled = standardFlowEnabled;
}

public Boolean isImplicitFlowEnabled() {
return implicitFlowEnabled;
}

public void setImplicitFlowEnabled(Boolean implicitFlowEnabled) {
this.implicitFlowEnabled = implicitFlowEnabled;
}

public Boolean isDirectAccessGrantsEnabled() {
return directAccessGrantsEnabled;
}

public void setDirectAccessGrantsEnabled(Boolean directAccessGrantsEnabled) {
this.directAccessGrantsEnabled = directAccessGrantsEnabled;
}

public Boolean isServiceAccountsEnabled() {
return serviceAccountsEnabled;
}
Expand Down
Expand Up @@ -169,14 +169,18 @@ client.name.tooltip=Specifies display name of the client. For example 'My Client
client.enabled.tooltip=Disabled clients cannot initiate a login or have obtain access tokens.
consent-required=Consent Required
consent-required.tooltip=If enabled users have to consent to client access.
direct-grants-only=Direct Grants Only
direct-grants-only.tooltip=When enabled, client can only obtain grants from grant REST API.
client-protocol=Client Protocol
client-protocol.tooltip='OpenID connect' allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server.'SAML' enables web-based authentication and authorization scenarios including cross-domain single sign-on (SSO) and uses security tokens containing assertions to pass information.
access-type=Access Type
access-type.tooltip='Confidential' clients require a secret to initiate login protocol. 'Public' clients do not require a secret. 'Bearer-only' clients are web services that never initiate a login.
standard-flow-enabled=Standard Flow Enabled
standard-flow-enabled.tooltip=This enables standard OpenID Connect redirect based authentication with authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Authorization Code Flow' for this client.
implicit-flow-enabled=Implicit Flow Enabled
implicit-flow-enabled.tooltip=This enables support for OpenID Connect redirect based authentication without authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Implicit Flow' for this client.
direct-access-grants-enabled=Direct Access Grants Enabled
direct-access-grants-enabled.tooltip=This enables support for Direct Access Grants, which means that client has access to username/password of user and exchange it directly with Keycloak server for access token. In terms of OAuth2 specification, this enables support of 'Resource Owner Password Credentials Grant' for this client.
service-accounts-enabled=Service Accounts Enabled
service-accounts-enabled.tooltip=Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client.
service-accounts-enabled.tooltip=Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client. In terms of OAuth2 specification, this enables support of 'Client Credentials Grant' for this client.
include-authnstatement=Include AuthnStatement
include-authnstatement.tooltip=Should a statement specifying the method and timestamp be included in login responses?
sign-documents=Sign Documents
Expand Down
Expand Up @@ -862,7 +862,12 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
$scope.client = angular.copy(client);
updateProperties();
} else {
$scope.client = { enabled: true, attributes: {}};
$scope.client = {
enabled: true,
standardFlowEnabled: true,
directAccessGrantsEnabled: true,
attributes: {}
};
$scope.client.attributes['saml_signature_canonicalization_method'] = $scope.canonicalization[0].value;
$scope.client.redirectUris = [];
$scope.accessType = $scope.accessTypes[0];
Expand Down Expand Up @@ -1039,7 +1044,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
$scope.client.attributes['saml.signature.algorithm'] = $scope.signatureAlgorithm;
$scope.client.attributes['saml_name_id_format'] = $scope.nameIdFormat;

if ($scope.client.protocol != 'saml' && !$scope.client.bearerOnly && !$scope.client.directGrantsOnly && (!$scope.client.redirectUris || $scope.client.redirectUris.length == 0)) {
if ($scope.client.protocol != 'saml' && !$scope.client.bearerOnly && ($scope.client.standardFlowEnabled || $scope.client.implicitFlowEnabled) && (!$scope.client.redirectUris || $scope.client.redirectUris.length == 0)) {
Notifications.error("You must specify at least one redirect uri");
} else {
if ($scope.create) {
Expand Down
Expand Up @@ -59,13 +59,6 @@
</div>
<kc-tooltip>{{:: 'consent-required.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix block">
<label class="col-md-2 control-label" for="directGrantsOnly">{{:: 'direct-grants-only' | translate}}</label>
<div class="col-sm-6">
<input ng-model="client.directGrantsOnly" name="directGrantsOnly" id="directGrantsOnly" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
<kc-tooltip>{{:: 'direct-grants-only.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="protocol">{{:: 'client-protocol' | translate}}</label>
<div class="col-sm-6">
Expand All @@ -92,6 +85,27 @@
</div>
<kc-tooltip>{{:: 'access-type.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="protocol == 'openid-connect' && !client.bearerOnly">
<label class="col-md-2 control-label" for="standardFlowEnabled">{{:: 'standard-flow-enabled' | translate}}</label>
<kc-tooltip>{{:: 'standard-flow-enabled.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<input ng-model="client.standardFlowEnabled" name="standardFlowEnabled" id="standardFlowEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
</div>
<div class="form-group" data-ng-show="protocol == 'openid-connect' && client.publicClient && !client.bearerOnly">
<label class="col-md-2 control-label" for="implicitFlowEnabled">{{:: 'implicit-flow-enabled' | translate}}</label>
<kc-tooltip>{{:: 'implicit-flow-enabled.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<input ng-model="client.implicitFlowEnabled" name="implicitFlowEnabled" id="implicitFlowEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
</div>
<div class="form-group" data-ng-show="protocol == 'openid-connect' && !client.bearerOnly">
<label class="col-md-2 control-label" for="directAccessGrantsEnabled">{{:: 'direct-access-grants-enabled' | translate}}</label>
<kc-tooltip>{{:: 'direct-access-grants-enabled.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<input ng-model="client.directAccessGrantsEnabled" name="directAccessGrantsEnabled" id="directAccessGrantsEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
</div>
<div class="form-group" data-ng-show="protocol == 'openid-connect' && !client.publicClient && !client.bearerOnly">
<label class="col-md-2 control-label" for="serviceAccountsEnabled">{{:: 'service-accounts-enabled' | translate}}</label>
<kc-tooltip>{{:: 'service-accounts-enabled.tooltip' | translate}}</kc-tooltip>
Expand Down Expand Up @@ -202,7 +216,7 @@
<kc-tooltip>{{:: 'root-url.tooltip' | translate}}</kc-tooltip>
</div>

<div class="form-group clearfix block" data-ng-hide="client.bearerOnly || client.directGrantsOnly">
<div class="form-group clearfix block" data-ng-hide="client.bearerOnly || (!client.standardFlowEnabled && !client.implicitFlowEnabled)">
<label class="col-md-2 control-label" for="newRedirectUri"><span class="required" data-ng-show="protocol != 'saml'">*</span> {{:: 'valid-redirect-uris' | translate}}</label>

<div class="col-sm-6">
Expand Down
Expand Up @@ -176,7 +176,7 @@ failedLogout=Logout failed
unknownLoginRequesterMessage=Unknown login requester
loginRequesterNotEnabledMessage=Login requester not enabled
bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login
directGrantsOnlyMessage=Direct-grants-only clients are not allowed to initiate browser login
standardFlowDisabledMessage=Client is not allowed to initiate browser login because standard flow is disabled for the client.
invalidRedirectUriMessage=Invalid redirect uri
unsupportedNameIdFormatMessage=Unsupported NameIDFormat
invlidRequesterMessage=Invalid requester
Expand Down
12 changes: 9 additions & 3 deletions model/api/src/main/java/org/keycloak/models/ClientModel.java
Expand Up @@ -111,12 +111,18 @@ public interface ClientModel extends RoleContainerModel {
boolean isPublicClient();
void setPublicClient(boolean flag);

boolean isDirectGrantsOnly();
void setDirectGrantsOnly(boolean flag);

boolean isConsentRequired();
void setConsentRequired(boolean consentRequired);

boolean isStandardFlowEnabled();
void setStandardFlowEnabled(boolean standardFlowEnabled);

boolean isImplicitFlowEnabled();
void setImplicitFlowEnabled(boolean implicitFlowEnabled);

boolean isDirectAccessGrantsEnabled();
void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled);

boolean isServiceAccountsEnabled();
void setServiceAccountsEnabled(boolean serviceAccountsEnabled);

Expand Down
Expand Up @@ -30,6 +30,9 @@ public class ClientEntity extends AbstractIdentifiableEntity {
private String baseUrl;
private boolean bearerOnly;
private boolean consentRequired;
private boolean standardFlowEnabled;
private boolean implicitFlowEnabled;
private boolean directAccessGrantsEnabled;
private boolean serviceAccountsEnabled;
private boolean directGrantsOnly;
private int nodeReRegistrationTimeout;
Expand Down Expand Up @@ -243,6 +246,30 @@ public void setConsentRequired(boolean consentRequired) {
this.consentRequired = consentRequired;
}

public boolean isStandardFlowEnabled() {
return standardFlowEnabled;
}

public void setStandardFlowEnabled(boolean standardFlowEnabled) {
this.standardFlowEnabled = standardFlowEnabled;
}

public boolean isImplicitFlowEnabled() {
return implicitFlowEnabled;
}

public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
this.implicitFlowEnabled = implicitFlowEnabled;
}

public boolean isDirectAccessGrantsEnabled() {
return directAccessGrantsEnabled;
}

public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
this.directAccessGrantsEnabled = directAccessGrantsEnabled;
}

public boolean isServiceAccountsEnabled() {
return serviceAccountsEnabled;
}
Expand Down
Expand Up @@ -418,8 +418,10 @@ public static ClientRepresentation toRepresentation(ClientModel clientModel) {
rep.setFullScopeAllowed(clientModel.isFullScopeAllowed());
rep.setBearerOnly(clientModel.isBearerOnly());
rep.setConsentRequired(clientModel.isConsentRequired());
rep.setStandardFlowEnabled(clientModel.isStandardFlowEnabled());
rep.setImplicitFlowEnabled(clientModel.isImplicitFlowEnabled());
rep.setDirectAccessGrantsEnabled(clientModel.isDirectAccessGrantsEnabled());
rep.setServiceAccountsEnabled(clientModel.isServiceAccountsEnabled());
rep.setDirectGrantsOnly(clientModel.isDirectGrantsOnly());
rep.setSurrogateAuthRequired(clientModel.isSurrogateAuthRequired());
rep.setRootUrl(clientModel.getRootUrl());
rep.setBaseUrl(clientModel.getBaseUrl());
Expand Down
Expand Up @@ -772,8 +772,17 @@ public static ClientModel createClient(KeycloakSession session, RealmModel realm
if (resourceRep.getBaseUrl() != null) client.setBaseUrl(resourceRep.getBaseUrl());
if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
if (resourceRep.isStandardFlowEnabled() != null) client.setStandardFlowEnabled(resourceRep.isStandardFlowEnabled());
if (resourceRep.isImplicitFlowEnabled() != null) client.setImplicitFlowEnabled(resourceRep.isImplicitFlowEnabled());
if (resourceRep.isDirectAccessGrantsEnabled() != null) client.setDirectAccessGrantsEnabled(resourceRep.isDirectAccessGrantsEnabled());
if (resourceRep.isServiceAccountsEnabled() != null) client.setServiceAccountsEnabled(resourceRep.isServiceAccountsEnabled());
if (resourceRep.isDirectGrantsOnly() != null) client.setDirectGrantsOnly(resourceRep.isDirectGrantsOnly());

// Backwards compatibility only
if (resourceRep.isDirectGrantsOnly() != null) {
logger.warn("Using deprecated 'directGrantsOnly' configuration in JSON representation. It will be removed in future versions");
client.setStandardFlowEnabled(!resourceRep.isDirectGrantsOnly());
}

if (resourceRep.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient());
if (resourceRep.isFrontchannelLogout() != null) client.setFrontchannelLogout(resourceRep.isFrontchannelLogout());
if (resourceRep.getProtocol() != null) client.setProtocol(resourceRep.getProtocol());
Expand Down Expand Up @@ -869,8 +878,10 @@ public static void updateClient(ClientRepresentation rep, ClientModel resource)
if (rep.isEnabled() != null) resource.setEnabled(rep.isEnabled());
if (rep.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly());
if (rep.isConsentRequired() != null) resource.setConsentRequired(rep.isConsentRequired());
if (rep.isStandardFlowEnabled() != null) resource.setStandardFlowEnabled(rep.isStandardFlowEnabled());
if (rep.isImplicitFlowEnabled() != null) resource.setImplicitFlowEnabled(rep.isImplicitFlowEnabled());
if (rep.isDirectAccessGrantsEnabled() != null) resource.setDirectAccessGrantsEnabled(rep.isDirectAccessGrantsEnabled());
if (rep.isServiceAccountsEnabled() != null) resource.setServiceAccountsEnabled(rep.isServiceAccountsEnabled());
if (rep.isDirectGrantsOnly() != null) resource.setDirectGrantsOnly(rep.isDirectGrantsOnly());
if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient());
if (rep.isFullScopeAllowed() != null) resource.setFullScopeAllowed(rep.isFullScopeAllowed());
if (rep.isFrontchannelLogout() != null) resource.setFrontchannelLogout(rep.isFrontchannelLogout());
Expand Down

0 comments on commit 8d2e4c0

Please sign in to comment.