Skip to content

Commit

Permalink
Merge pull request keycloak#4932 from patriot1burke/per-client-flow
Browse files Browse the repository at this point in the history
KEYCLOAK-6335
  • Loading branch information
patriot1burke authored Jan 25, 2018
2 parents 3d12bf7 + 4bfb62d commit 7c66f76
Show file tree
Hide file tree
Showing 23 changed files with 746 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public class ClientRepresentation {
protected Boolean frontchannelLogout;
protected String protocol;
protected Map<String, String> attributes;
protected Map<String, String> authenticationFlowBindingOverrides;
protected Boolean fullScopeAllowed;
protected Integer nodeReRegistrationTimeout;
protected Map<String, Integer> registeredNodes;
Expand Down Expand Up @@ -296,6 +297,14 @@ public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}

public Map<String, String> getAuthenticationFlowBindingOverrides() {
return authenticationFlowBindingOverrides;
}

public void setAuthenticationFlowBindingOverrides(Map<String, String> authenticationFlowBindingOverrides) {
this.authenticationFlowBindingOverrides = authenticationFlowBindingOverrides;
}

public Integer getNodeReRegistrationTimeout() {
return nodeReRegistrationTimeout;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,34 @@ public Map<String, String> getAttributes() {
return copy;
}

@Override
public void setAuthenticationFlowBindingOverride(String name, String value) {
getDelegateForUpdate();
updated.setAuthenticationFlowBindingOverride(name, value);

}

@Override
public void removeAuthenticationFlowBindingOverride(String name) {
getDelegateForUpdate();
updated.removeAuthenticationFlowBindingOverride(name);

}

@Override
public String getAuthenticationFlowBindingOverride(String name) {
if (isUpdated()) return updated.getAuthenticationFlowBindingOverride(name);
return cached.getAuthFlowBindings().get(name);
}

@Override
public Map<String, String> getAuthenticationFlowBindingOverrides() {
if (isUpdated()) return updated.getAuthenticationFlowBindingOverrides();
Map<String, String> copy = new HashMap<String, String>();
copy.putAll(cached.getAuthFlowBindings());
return copy;
}

@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
if (isUpdated()) return updated.getProtocolMappers();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class CachedClient extends AbstractRevisioned implements InRealm {
protected String registrationToken;
protected String protocol;
protected Map<String, String> attributes = new HashMap<String, String>();
protected Map<String, String> authFlowBindings = new HashMap<String, String>();
protected boolean publicClient;
protected boolean fullScopeAllowed;
protected boolean frontchannelLogout;
Expand Down Expand Up @@ -83,6 +84,7 @@ public CachedClient(Long revision, RealmModel realm, ClientModel model) {
enabled = model.isEnabled();
protocol = model.getProtocol();
attributes.putAll(model.getAttributes());
authFlowBindings.putAll(model.getAuthenticationFlowBindingOverrides());
notBefore = model.getNotBefore();
frontchannelLogout = model.isFrontchannelLogout();
publicClient = model.isPublicClient();
Expand Down Expand Up @@ -256,4 +258,8 @@ public boolean isUseTemplateConfig() {
public boolean isUseTemplateMappers() {
return useTemplateMappers;
}

public Map<String, String> getAuthFlowBindings() {
return authFlowBindings;
}
}
23 changes: 23 additions & 0 deletions model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,29 @@ public void setProtocol(String protocol) {

}

@Override
public void setAuthenticationFlowBindingOverride(String name, String value) {
entity.getAuthFlowBindings().put(name, value);

}

@Override
public void removeAuthenticationFlowBindingOverride(String name) {
entity.getAuthFlowBindings().remove(name);
}

@Override
public String getAuthenticationFlowBindingOverride(String name) {
return entity.getAuthFlowBindings().get(name);
}

@Override
public Map<String, String> getAuthenticationFlowBindingOverrides() {
Map<String, String> copy = new HashMap<>();
copy.putAll(entity.getAuthFlowBindings());
return copy;
}

@Override
public void setAttribute(String name, String value) {
entity.getAttributes().put(name, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ public class ClientEntity {
@CollectionTable(name="CLIENT_ATTRIBUTES", joinColumns={ @JoinColumn(name="CLIENT_ID") })
protected Map<String, String> attributes = new HashMap<String, String>();

@ElementCollection
@MapKeyColumn(name="BINDING_NAME")
@Column(name="FLOW_ID", length = 4000)
@CollectionTable(name="CLIENT_AUTH_FLOW_BINDINGS", joinColumns={ @JoinColumn(name="CLIENT_ID") })
protected Map<String, String> authFlowBindings = new HashMap<String, String>();

@OneToMany(fetch = FetchType.LAZY, mappedBy = "client", cascade = CascadeType.REMOVE)
Collection<ClientIdentityProviderMappingEntity> identityProviders = new ArrayList<ClientIdentityProviderMappingEntity>();

Expand Down Expand Up @@ -292,6 +298,14 @@ public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}

public Map<String, String> getAuthFlowBindings() {
return authFlowBindings;
}

public void setAuthFlowBindings(Map<String, String> authFlowBindings) {
this.authFlowBindings = authFlowBindings;
}

public String getProtocol() {
return protocol;
}
Expand Down
32 changes: 32 additions & 0 deletions model/jpa/src/main/resources/META-INF/jpa-changelog-4.0.0.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ * Copyright 2017 Red Hat, Inc. and/or its affiliates
~ * and other contributors as indicated by the @author tags.
~ *
~ * Licensed under the Apache License, Version 2.0 (the "License");
~ * you may not use this file except in compliance with the License.
~ * You may obtain a copy of the License at
~ *
~ * http://www.apache.org/licenses/LICENSE-2.0
~ *
~ * Unless required by applicable law or agreed to in writing, software
~ * distributed under the License is distributed on an "AS IS" BASIS,
~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ * See the License for the specific language governing permissions and
~ * limitations under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">

<changeSet author="bburke@redhat.com" id="4.0.0-KEYCLOAK-6335">
<createTable tableName="CLIENT_AUTH_FLOW_BINDINGS">
<column name="CLIENT_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="FLOW_ID" type="VARCHAR(36)"/>
<column name="BINDING_NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
<addPrimaryKey columnNames="CLIENT_ID, BINDING_NAME" constraintName="C_CLI_FLOW_BIND" tableName="CLIENT_AUTH_FLOW_BINDINGS"/>
</changeSet>
</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,5 @@
<include file="META-INF/jpa-changelog-3.4.0.xml"/>
<include file="META-INF/jpa-changelog-3.4.1.xml"/>
<include file="META-INF/jpa-changelog-3.4.2.xml"/>
<include file="META-INF/jpa-changelog-4.0.0.xml"/>
</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.utils;

import org.keycloak.models.AuthenticationFlowBindings;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ModelException;
import org.keycloak.sessions.AuthenticationSessionModel;

/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AuthenticationFlowResolver {

public static AuthenticationFlowModel resolveBrowserFlow(AuthenticationSessionModel authSession) {
AuthenticationFlowModel flow = null;
ClientModel client = authSession.getClient();
String clientFlow = client.getAuthenticationFlowBindingOverride(AuthenticationFlowBindings.BROWSER_BINDING);
if (clientFlow != null) {
flow = authSession.getRealm().getAuthenticationFlowById(clientFlow);
if (flow == null) {
throw new ModelException("Client " + client.getClientId() + " has browser flow override, but this flow does not exist");
}
return flow;
}
return authSession.getRealm().getBrowserFlow();
}
public static AuthenticationFlowModel resolveDirectGrantFlow(AuthenticationSessionModel authSession) {
AuthenticationFlowModel flow = null;
ClientModel client = authSession.getClient();
String clientFlow = client.getAuthenticationFlowBindingOverride(AuthenticationFlowBindings.DIRECT_GRANT_BINDING);
if (clientFlow != null) {
flow = authSession.getRealm().getAuthenticationFlowById(clientFlow);
if (flow == null) {
throw new ModelException("Client " + client.getClientId() + " has direct grant flow override, but this flow does not exist");
}
return flow;
}
return authSession.getRealm().getDirectGrantFlow();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ public static ClientRepresentation toRepresentation(ClientModel clientModel) {
rep.setFrontchannelLogout(clientModel.isFrontchannelLogout());
rep.setProtocol(clientModel.getProtocol());
rep.setAttributes(clientModel.getAttributes());
rep.setAuthenticationFlowBindingOverrides(clientModel.getAuthenticationFlowBindingOverrides());
rep.setFullScopeAllowed(clientModel.isFullScopeAllowed());
rep.setBearerOnly(clientModel.isBearerOnly());
rep.setConsentRequired(clientModel.isConsentRequired());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,17 @@ public static ClientModel createClient(KeycloakSession session, RealmModel realm
}


if (resourceRep.getAuthenticationFlowBindingOverrides() != null) {
for (Map.Entry<String, String> entry : resourceRep.getAuthenticationFlowBindingOverrides().entrySet()) {
if (entry.getValue() == null || entry.getValue().trim().equals("")) {
continue;
} else {
client.setAuthenticationFlowBindingOverride(entry.getKey(), entry.getValue());
}
}
}


if (resourceRep.getRedirectUris() != null) {
for (String redirectUri : resourceRep.getRedirectUris()) {
client.addRedirectUri(redirectUri);
Expand Down Expand Up @@ -1201,6 +1212,22 @@ public static void updateClient(ClientRepresentation rep, ClientModel resource)
resource.setAttribute(entry.getKey(), entry.getValue());
}
}
if (rep.getAttributes() != null) {
for (Map.Entry<String, String> entry : rep.getAttributes().entrySet()) {
resource.setAttribute(entry.getKey(), entry.getValue());
}
}

if (rep.getAuthenticationFlowBindingOverrides() != null) {
for (Map.Entry<String, String> entry : rep.getAuthenticationFlowBindingOverrides().entrySet()) {
if (entry.getValue() == null || entry.getValue().trim().equals("")) {
resource.removeAuthenticationFlowBindingOverride(entry.getKey());
} else {
resource.setAuthenticationFlowBindingOverride(entry.getKey(), entry.getValue());

}
}
}


if (rep.getNotBefore() != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models;

/**
* Defines constants for authentication flow bindings. Strings used for lookup
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface AuthenticationFlowBindings {
String BROWSER_BINDING = "browser";
String DIRECT_GRANT_BINDING = "direct_grant";
}
12 changes: 12 additions & 0 deletions server-spi/src/main/java/org/keycloak/models/ClientModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,18 @@ public interface ClientModel extends RoleContainerModel, ProtocolMapperContaine
String getAttribute(String name);
Map<String, String> getAttributes();

/**
* Get authentication flow binding override for this client. Allows client to override an authentication flow binding.
*
* @param binding examples are "browser", "direct_grant"
*
* @return
*/
public String getAuthenticationFlowBindingOverride(String binding);
public Map<String, String> getAuthenticationFlowBindingOverrides();
public void removeAuthenticationFlowBindingOverride(String binding);
public void setAuthenticationFlowBindingOverride(String binding, String flowId);

boolean isFrontchannelLogout();
void setFrontchannelLogout(boolean flag);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.AuthenticationFlowResolver;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.LoginProtocol;
Expand Down Expand Up @@ -646,7 +647,7 @@ public Response handleBrowserException(Exception failure) {
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setAuthenticationSession(clone)
.setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
.setFlowId(realm.getBrowserFlow().getId())
.setFlowId(AuthenticationFlowResolver.resolveBrowserFlow(clone).getId())
.setForwardedErrorMessage(reset.getErrorMessage())
.setForwardedSuccessMessage(reset.getSuccessMessage())
.setConnection(connection)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.AuthenticationFlowResolver;
import org.keycloak.protocol.LoginProtocol.Error;
import org.keycloak.services.ErrorPageException;
import org.keycloak.services.managers.AuthenticationManager;
Expand Down Expand Up @@ -107,7 +108,7 @@ protected AuthenticationProcessor createProcessor(AuthenticationSessionModel aut
* @return response to be returned to the browser
*/
protected Response handleBrowserAuthenticationRequest(AuthenticationSessionModel authSession, LoginProtocol protocol, boolean isPassive, boolean redirectToAuthentication) {
AuthenticationFlowModel flow = getAuthenticationFlow();
AuthenticationFlowModel flow = getAuthenticationFlow(authSession);
String flowId = flow.getId();
AuthenticationProcessor processor = createProcessor(authSession, flowId, LoginActionsService.AUTHENTICATE_PATH);
event.detail(Details.CODE_ID, authSession.getParentSession().getId());
Expand Down Expand Up @@ -149,8 +150,8 @@ protected Response handleBrowserAuthenticationRequest(AuthenticationSessionModel
}
}

protected AuthenticationFlowModel getAuthenticationFlow() {
return realm.getBrowserFlow();
protected AuthenticationFlowModel getAuthenticationFlow(AuthenticationSessionModel authSession) {
return AuthenticationFlowResolver.resolveBrowserFlow(authSession);
}

protected void checkSsl() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private void updateAuthenticationSession() {
}

@Override
protected AuthenticationFlowModel getAuthenticationFlow() {
protected AuthenticationFlowModel getAuthenticationFlow(AuthenticationSessionModel authSession) {
return realm.getDockerAuthenticationFlow();
}

Expand Down
Loading

0 comments on commit 7c66f76

Please sign in to comment.