Merge pull request #4932 from patriot1burke/per-client-flow
KEYCLOAK-6335
This commit is contained in:
commit
7c66f76858
23 changed files with 746 additions and 10 deletions
|
@ -56,6 +56,7 @@ public class ClientRepresentation {
|
||||||
protected Boolean frontchannelLogout;
|
protected Boolean frontchannelLogout;
|
||||||
protected String protocol;
|
protected String protocol;
|
||||||
protected Map<String, String> attributes;
|
protected Map<String, String> attributes;
|
||||||
|
protected Map<String, String> authenticationFlowBindingOverrides;
|
||||||
protected Boolean fullScopeAllowed;
|
protected Boolean fullScopeAllowed;
|
||||||
protected Integer nodeReRegistrationTimeout;
|
protected Integer nodeReRegistrationTimeout;
|
||||||
protected Map<String, Integer> registeredNodes;
|
protected Map<String, Integer> registeredNodes;
|
||||||
|
@ -296,6 +297,14 @@ public class ClientRepresentation {
|
||||||
this.attributes = attributes;
|
this.attributes = attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getAuthenticationFlowBindingOverrides() {
|
||||||
|
return authenticationFlowBindingOverrides;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticationFlowBindingOverrides(Map<String, String> authenticationFlowBindingOverrides) {
|
||||||
|
this.authenticationFlowBindingOverrides = authenticationFlowBindingOverrides;
|
||||||
|
}
|
||||||
|
|
||||||
public Integer getNodeReRegistrationTimeout() {
|
public Integer getNodeReRegistrationTimeout() {
|
||||||
return nodeReRegistrationTimeout;
|
return nodeReRegistrationTimeout;
|
||||||
}
|
}
|
||||||
|
|
|
@ -345,6 +345,34 @@ public class ClientAdapter implements ClientModel {
|
||||||
return copy;
|
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
|
@Override
|
||||||
public Set<ProtocolMapperModel> getProtocolMappers() {
|
public Set<ProtocolMapperModel> getProtocolMappers() {
|
||||||
if (isUpdated()) return updated.getProtocolMappers();
|
if (isUpdated()) return updated.getProtocolMappers();
|
||||||
|
|
|
@ -46,6 +46,7 @@ public class CachedClient extends AbstractRevisioned implements InRealm {
|
||||||
protected String registrationToken;
|
protected String registrationToken;
|
||||||
protected String protocol;
|
protected String protocol;
|
||||||
protected Map<String, String> attributes = new HashMap<String, String>();
|
protected Map<String, String> attributes = new HashMap<String, String>();
|
||||||
|
protected Map<String, String> authFlowBindings = new HashMap<String, String>();
|
||||||
protected boolean publicClient;
|
protected boolean publicClient;
|
||||||
protected boolean fullScopeAllowed;
|
protected boolean fullScopeAllowed;
|
||||||
protected boolean frontchannelLogout;
|
protected boolean frontchannelLogout;
|
||||||
|
@ -83,6 +84,7 @@ public class CachedClient extends AbstractRevisioned implements InRealm {
|
||||||
enabled = model.isEnabled();
|
enabled = model.isEnabled();
|
||||||
protocol = model.getProtocol();
|
protocol = model.getProtocol();
|
||||||
attributes.putAll(model.getAttributes());
|
attributes.putAll(model.getAttributes());
|
||||||
|
authFlowBindings.putAll(model.getAuthenticationFlowBindingOverrides());
|
||||||
notBefore = model.getNotBefore();
|
notBefore = model.getNotBefore();
|
||||||
frontchannelLogout = model.isFrontchannelLogout();
|
frontchannelLogout = model.isFrontchannelLogout();
|
||||||
publicClient = model.isPublicClient();
|
publicClient = model.isPublicClient();
|
||||||
|
@ -256,4 +258,8 @@ public class CachedClient extends AbstractRevisioned implements InRealm {
|
||||||
public boolean isUseTemplateMappers() {
|
public boolean isUseTemplateMappers() {
|
||||||
return useTemplateMappers;
|
return useTemplateMappers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getAuthFlowBindings() {
|
||||||
|
return authFlowBindings;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -270,6 +270,29 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
public void setAttribute(String name, String value) {
|
public void setAttribute(String name, String value) {
|
||||||
entity.getAttributes().put(name, value);
|
entity.getAttributes().put(name, value);
|
||||||
|
|
|
@ -119,6 +119,12 @@ public class ClientEntity {
|
||||||
@CollectionTable(name="CLIENT_ATTRIBUTES", joinColumns={ @JoinColumn(name="CLIENT_ID") })
|
@CollectionTable(name="CLIENT_ATTRIBUTES", joinColumns={ @JoinColumn(name="CLIENT_ID") })
|
||||||
protected Map<String, String> attributes = new HashMap<String, String>();
|
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)
|
@OneToMany(fetch = FetchType.LAZY, mappedBy = "client", cascade = CascadeType.REMOVE)
|
||||||
Collection<ClientIdentityProviderMappingEntity> identityProviders = new ArrayList<ClientIdentityProviderMappingEntity>();
|
Collection<ClientIdentityProviderMappingEntity> identityProviders = new ArrayList<ClientIdentityProviderMappingEntity>();
|
||||||
|
|
||||||
|
@ -292,6 +298,14 @@ public class ClientEntity {
|
||||||
this.attributes = attributes;
|
this.attributes = attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getAuthFlowBindings() {
|
||||||
|
return authFlowBindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthFlowBindings(Map<String, String> authFlowBindings) {
|
||||||
|
this.authFlowBindings = authFlowBindings;
|
||||||
|
}
|
||||||
|
|
||||||
public String getProtocol() {
|
public String getProtocol() {
|
||||||
return protocol;
|
return protocol;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -53,4 +53,5 @@
|
||||||
<include file="META-INF/jpa-changelog-3.4.0.xml"/>
|
<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.1.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-3.4.2.xml"/>
|
<include file="META-INF/jpa-changelog-3.4.2.xml"/>
|
||||||
|
<include file="META-INF/jpa-changelog-4.0.0.xml"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -494,6 +494,7 @@ public class ModelToRepresentation {
|
||||||
rep.setFrontchannelLogout(clientModel.isFrontchannelLogout());
|
rep.setFrontchannelLogout(clientModel.isFrontchannelLogout());
|
||||||
rep.setProtocol(clientModel.getProtocol());
|
rep.setProtocol(clientModel.getProtocol());
|
||||||
rep.setAttributes(clientModel.getAttributes());
|
rep.setAttributes(clientModel.getAttributes());
|
||||||
|
rep.setAuthenticationFlowBindingOverrides(clientModel.getAuthenticationFlowBindingOverrides());
|
||||||
rep.setFullScopeAllowed(clientModel.isFullScopeAllowed());
|
rep.setFullScopeAllowed(clientModel.isFullScopeAllowed());
|
||||||
rep.setBearerOnly(clientModel.isBearerOnly());
|
rep.setBearerOnly(clientModel.isBearerOnly());
|
||||||
rep.setConsentRequired(clientModel.isConsentRequired());
|
rep.setConsentRequired(clientModel.isConsentRequired());
|
||||||
|
|
|
@ -1084,6 +1084,17 @@ public class RepresentationToModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
if (resourceRep.getRedirectUris() != null) {
|
||||||
for (String redirectUri : resourceRep.getRedirectUris()) {
|
for (String redirectUri : resourceRep.getRedirectUris()) {
|
||||||
client.addRedirectUri(redirectUri);
|
client.addRedirectUri(redirectUri);
|
||||||
|
@ -1201,6 +1212,22 @@ public class RepresentationToModel {
|
||||||
resource.setAttribute(entry.getKey(), entry.getValue());
|
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) {
|
if (rep.getNotBefore() != null) {
|
||||||
|
|
|
@ -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";
|
||||||
|
}
|
|
@ -118,6 +118,18 @@ public interface ClientModel extends RoleContainerModel, ProtocolMapperContaine
|
||||||
String getAttribute(String name);
|
String getAttribute(String name);
|
||||||
Map<String, String> getAttributes();
|
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();
|
boolean isFrontchannelLogout();
|
||||||
void setFrontchannelLogout(boolean flag);
|
void setFrontchannelLogout(boolean flag);
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
|
@ -646,7 +647,7 @@ public class AuthenticationProcessor {
|
||||||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||||
processor.setAuthenticationSession(clone)
|
processor.setAuthenticationSession(clone)
|
||||||
.setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
|
.setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
|
||||||
.setFlowId(realm.getBrowserFlow().getId())
|
.setFlowId(AuthenticationFlowResolver.resolveBrowserFlow(clone).getId())
|
||||||
.setForwardedErrorMessage(reset.getErrorMessage())
|
.setForwardedErrorMessage(reset.getErrorMessage())
|
||||||
.setForwardedSuccessMessage(reset.getSuccessMessage())
|
.setForwardedSuccessMessage(reset.getSuccessMessage())
|
||||||
.setConnection(connection)
|
.setConnection(connection)
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
||||||
import org.keycloak.protocol.LoginProtocol.Error;
|
import org.keycloak.protocol.LoginProtocol.Error;
|
||||||
import org.keycloak.services.ErrorPageException;
|
import org.keycloak.services.ErrorPageException;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
@ -107,7 +108,7 @@ public abstract class AuthorizationEndpointBase {
|
||||||
* @return response to be returned to the browser
|
* @return response to be returned to the browser
|
||||||
*/
|
*/
|
||||||
protected Response handleBrowserAuthenticationRequest(AuthenticationSessionModel authSession, LoginProtocol protocol, boolean isPassive, boolean redirectToAuthentication) {
|
protected Response handleBrowserAuthenticationRequest(AuthenticationSessionModel authSession, LoginProtocol protocol, boolean isPassive, boolean redirectToAuthentication) {
|
||||||
AuthenticationFlowModel flow = getAuthenticationFlow();
|
AuthenticationFlowModel flow = getAuthenticationFlow(authSession);
|
||||||
String flowId = flow.getId();
|
String flowId = flow.getId();
|
||||||
AuthenticationProcessor processor = createProcessor(authSession, flowId, LoginActionsService.AUTHENTICATE_PATH);
|
AuthenticationProcessor processor = createProcessor(authSession, flowId, LoginActionsService.AUTHENTICATE_PATH);
|
||||||
event.detail(Details.CODE_ID, authSession.getParentSession().getId());
|
event.detail(Details.CODE_ID, authSession.getParentSession().getId());
|
||||||
|
@ -149,8 +150,8 @@ public abstract class AuthorizationEndpointBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AuthenticationFlowModel getAuthenticationFlow() {
|
protected AuthenticationFlowModel getAuthenticationFlow(AuthenticationSessionModel authSession) {
|
||||||
return realm.getBrowserFlow();
|
return AuthenticationFlowResolver.resolveBrowserFlow(authSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void checkSsl() {
|
protected void checkSsl() {
|
||||||
|
|
|
@ -88,7 +88,7 @@ public class DockerEndpoint extends AuthorizationEndpointBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AuthenticationFlowModel getAuthenticationFlow() {
|
protected AuthenticationFlowModel getAuthenticationFlow(AuthenticationSessionModel authSession) {
|
||||||
return realm.getDockerAuthenticationFlow();
|
return realm.getDockerAuthenticationFlow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
|
@ -491,7 +492,7 @@ public class TokenEndpoint {
|
||||||
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
|
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
|
||||||
authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
|
authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
|
||||||
|
|
||||||
AuthenticationFlowModel flow = realm.getDirectGrantFlow();
|
AuthenticationFlowModel flow = AuthenticationFlowResolver.resolveDirectGrantFlow(authSession);
|
||||||
String flowId = flow.getId();
|
String flowId = flow.getId();
|
||||||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||||
processor.setAuthenticationSession(authSession)
|
processor.setAuthenticationSession(authSession)
|
||||||
|
|
|
@ -147,7 +147,7 @@ public class SamlEcpProfileService extends SamlService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AuthenticationFlowModel getAuthenticationFlow() {
|
protected AuthenticationFlowModel getAuthenticationFlow(AuthenticationSessionModel authSession) {
|
||||||
for (AuthenticationFlowModel flowModel : realm.getAuthenticationFlows()) {
|
for (AuthenticationFlowModel flowModel : realm.getAuthenticationFlows()) {
|
||||||
if (flowModel.getAlias().equals(DefaultAuthenticationFlows.SAML_ECP_FLOW)) {
|
if (flowModel.getAlias().equals(DefaultAuthenticationFlows.SAML_ECP_FLOW)) {
|
||||||
return flowModel;
|
return flowModel;
|
||||||
|
|
|
@ -55,6 +55,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
import org.keycloak.protocol.LoginProtocolFactory;
|
import org.keycloak.protocol.LoginProtocolFactory;
|
||||||
|
@ -1121,7 +1122,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||||
|
|
||||||
protected Response browserAuthentication(AuthenticationSessionModel authSession, String errorMessage) {
|
protected Response browserAuthentication(AuthenticationSessionModel authSession, String errorMessage) {
|
||||||
this.event.event(EventType.LOGIN);
|
this.event.event(EventType.LOGIN);
|
||||||
AuthenticationFlowModel flow = realmModel.getBrowserFlow();
|
AuthenticationFlowModel flow = AuthenticationFlowResolver.resolveBrowserFlow(authSession);
|
||||||
String flowId = flow.getId();
|
String flowId = flow.getId();
|
||||||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||||
processor.setAuthenticationSession(authSession)
|
processor.setAuthenticationSession(authSession)
|
||||||
|
|
|
@ -53,6 +53,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.models.utils.SystemClientUtil;
|
import org.keycloak.models.utils.SystemClientUtil;
|
||||||
import org.keycloak.protocol.AuthorizationEndpointBase;
|
import org.keycloak.protocol.AuthorizationEndpointBase;
|
||||||
|
@ -252,7 +253,7 @@ public class LoginActionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response processAuthentication(boolean action, String execution, AuthenticationSessionModel authSession, String errorMessage) {
|
protected Response processAuthentication(boolean action, String execution, AuthenticationSessionModel authSession, String errorMessage) {
|
||||||
return processFlow(action, execution, authSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), errorMessage, new AuthenticationProcessor());
|
return processFlow(action, execution, authSession, AUTHENTICATE_PATH, AuthenticationFlowResolver.resolveBrowserFlow(authSession), errorMessage, new AuthenticationProcessor());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response processFlow(boolean action, String execution, AuthenticationSessionModel authSession, String flowPath, AuthenticationFlowModel flow, String errorMessage, AuthenticationProcessor processor) {
|
protected Response processFlow(boolean action, String execution, AuthenticationSessionModel authSession, String flowPath, AuthenticationFlowModel flow, String errorMessage, AuthenticationProcessor processor) {
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
/*
|
||||||
|
* 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.testsuite.forms;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||||
|
import org.keycloak.authentication.AuthenticationFlowError;
|
||||||
|
import org.keycloak.authentication.Authenticator;
|
||||||
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class UsernameOnlyAuthenticator implements Authenticator, AuthenticatorFactory {
|
||||||
|
public static final String PROVIDER_ID = "testsuite-username";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void authenticate(AuthenticationFlowContext context) {
|
||||||
|
String username = context.getHttpRequest().getDecodedFormParameters().getFirst("username");
|
||||||
|
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
|
||||||
|
if (user == null) {
|
||||||
|
context.failure(AuthenticationFlowError.UNKNOWN_USER);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.setUser(user);
|
||||||
|
context.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresUser() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void action(AuthenticationFlowContext context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Testsuite Username Only";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getReferenceCategory() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfigurable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||||
|
AuthenticationExecutionModel.Requirement.REQUIRED
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||||
|
return REQUIREMENT_CHOICES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserSetupAllowed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Testsuite Username authenticator. Username parameter sets username";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authenticator create(KeycloakSession session) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,4 +19,5 @@ org.keycloak.testsuite.forms.PassThroughAuthenticator
|
||||||
org.keycloak.testsuite.forms.PassThroughRegistration
|
org.keycloak.testsuite.forms.PassThroughRegistration
|
||||||
org.keycloak.testsuite.forms.ClickThroughAuthenticator
|
org.keycloak.testsuite.forms.ClickThroughAuthenticator
|
||||||
org.keycloak.testsuite.authentication.ExpectedParamAuthenticatorFactory
|
org.keycloak.testsuite.authentication.ExpectedParamAuthenticatorFactory
|
||||||
org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory
|
org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory
|
||||||
|
org.keycloak.testsuite.forms.UsernameOnlyAuthenticator
|
|
@ -180,6 +180,8 @@ public class ProvidersTest extends AbstractAuthenticationTest {
|
||||||
"Testsuite Dummy authenticator. Just passes through and is hardcoded to a specific user");
|
"Testsuite Dummy authenticator. Just passes through and is hardcoded to a specific user");
|
||||||
addProviderInfo(result, "testsuite-dummy-registration", "Testsuite Dummy Pass Thru",
|
addProviderInfo(result, "testsuite-dummy-registration", "Testsuite Dummy Pass Thru",
|
||||||
"Testsuite Dummy authenticator. Just passes through and is hardcoded to a specific user");
|
"Testsuite Dummy authenticator. Just passes through and is hardcoded to a specific user");
|
||||||
|
addProviderInfo(result, "testsuite-username", "Testsuite Username Only",
|
||||||
|
"Testsuite Username authenticator. Username parameter sets username");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,353 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.testsuite.forms;
|
||||||
|
|
||||||
|
import org.jboss.arquillian.container.test.api.Deployment;
|
||||||
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.admin.client.resource.ClientsResource;
|
||||||
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
|
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.AuthenticationFlowBindings;
|
||||||
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
|
import org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory;
|
||||||
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
|
import org.keycloak.testsuite.pages.ErrorPage;
|
||||||
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
|
||||||
|
import org.keycloak.util.BasicAuthHelper;
|
||||||
|
import org.openqa.selenium.By;
|
||||||
|
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import javax.ws.rs.client.Entity;
|
||||||
|
import javax.ws.rs.client.WebTarget;
|
||||||
|
import javax.ws.rs.core.Form;
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that clients can override auth flows
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
|
||||||
|
*/
|
||||||
|
public class FlowOverrideTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
|
public static final String TEST_APP_DIRECT_OVERRIDE = "test-app-direct-override";
|
||||||
|
public static final String TEST_APP_FLOW = "test-app-flow";
|
||||||
|
@Rule
|
||||||
|
public AssertEvents events = new AssertEvents(this);
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected AppPage appPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected ErrorPage errorPage;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deployment
|
||||||
|
public static WebArchive deploy() {
|
||||||
|
return RunOnServerDeployment.create(UserResource.class)
|
||||||
|
.addPackages(true, "org.keycloak.testsuite");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setupFlows() {
|
||||||
|
testingClient.server().run(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
|
|
||||||
|
ClientModel client = session.realms().getClientByClientId("test-app-flow", realm);
|
||||||
|
if (client != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client = session.realms().getClientByClientId("test-app", realm);
|
||||||
|
client.setDirectAccessGrantsEnabled(true);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Parent flow
|
||||||
|
AuthenticationFlowModel browser = new AuthenticationFlowModel();
|
||||||
|
browser.setAlias("parent-flow");
|
||||||
|
browser.setDescription("browser based authentication");
|
||||||
|
browser.setProviderId("basic-flow");
|
||||||
|
browser.setTopLevel(true);
|
||||||
|
browser.setBuiltIn(true);
|
||||||
|
browser = realm.addAuthenticationFlow(browser);
|
||||||
|
|
||||||
|
// Subflow2
|
||||||
|
AuthenticationFlowModel subflow2 = new AuthenticationFlowModel();
|
||||||
|
subflow2.setTopLevel(false);
|
||||||
|
subflow2.setBuiltIn(true);
|
||||||
|
subflow2.setAlias("subflow-2");
|
||||||
|
subflow2.setDescription("username+password AND pushButton");
|
||||||
|
subflow2.setProviderId("basic-flow");
|
||||||
|
subflow2 = realm.addAuthenticationFlow(subflow2);
|
||||||
|
|
||||||
|
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
|
||||||
|
execution.setParentFlow(browser.getId());
|
||||||
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
|
||||||
|
execution.setFlowId(subflow2.getId());
|
||||||
|
execution.setPriority(20);
|
||||||
|
execution.setAuthenticatorFlow(true);
|
||||||
|
realm.addAuthenticatorExecution(execution);
|
||||||
|
|
||||||
|
// Subflow2 - push the button
|
||||||
|
execution = new AuthenticationExecutionModel();
|
||||||
|
execution.setParentFlow(subflow2.getId());
|
||||||
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||||
|
execution.setAuthenticator(PushButtonAuthenticatorFactory.PROVIDER_ID);
|
||||||
|
execution.setPriority(10);
|
||||||
|
execution.setAuthenticatorFlow(false);
|
||||||
|
|
||||||
|
realm.addAuthenticatorExecution(execution);
|
||||||
|
|
||||||
|
// Subflow2 - username-password
|
||||||
|
execution = new AuthenticationExecutionModel();
|
||||||
|
execution.setParentFlow(subflow2.getId());
|
||||||
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||||
|
execution.setAuthenticator(UsernamePasswordFormFactory.PROVIDER_ID);
|
||||||
|
execution.setPriority(20);
|
||||||
|
execution.setAuthenticatorFlow(false);
|
||||||
|
|
||||||
|
realm.addAuthenticatorExecution(execution);
|
||||||
|
|
||||||
|
client = realm.addClient(TEST_APP_FLOW);
|
||||||
|
client.setSecret("password");
|
||||||
|
client.setBaseUrl("http://localhost:8180/auth/realms/master/app/auth");
|
||||||
|
client.setManagementUrl("http://localhost:8180/auth/realms/master/app/admin");
|
||||||
|
client.setEnabled(true);
|
||||||
|
client.addRedirectUri("http://localhost:8180/auth/realms/master/app/auth/*");
|
||||||
|
client.setAuthenticationFlowBindingOverride(AuthenticationFlowBindings.BROWSER_BINDING, browser.getId());
|
||||||
|
client.setPublicClient(false);
|
||||||
|
|
||||||
|
// Parent flow
|
||||||
|
AuthenticationFlowModel directGrant = new AuthenticationFlowModel();
|
||||||
|
directGrant.setAlias("direct-override-flow");
|
||||||
|
directGrant.setDescription("direct grant based authentication");
|
||||||
|
directGrant.setProviderId("basic-flow");
|
||||||
|
directGrant.setTopLevel(true);
|
||||||
|
directGrant.setBuiltIn(true);
|
||||||
|
directGrant = realm.addAuthenticationFlow(directGrant);
|
||||||
|
|
||||||
|
execution = new AuthenticationExecutionModel();
|
||||||
|
execution.setParentFlow(directGrant.getId());
|
||||||
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||||
|
execution.setAuthenticator(UsernameOnlyAuthenticator.PROVIDER_ID);
|
||||||
|
execution.setPriority(10);
|
||||||
|
execution.setAuthenticatorFlow(false);
|
||||||
|
|
||||||
|
realm.addAuthenticatorExecution(execution);
|
||||||
|
|
||||||
|
client = realm.addClient(TEST_APP_DIRECT_OVERRIDE);
|
||||||
|
client.setSecret("password");
|
||||||
|
client.setBaseUrl("http://localhost:8180/auth/realms/master/app/auth");
|
||||||
|
client.setManagementUrl("http://localhost:8180/auth/realms/master/app/admin");
|
||||||
|
client.setEnabled(true);
|
||||||
|
client.addRedirectUri("http://localhost:8180/auth/realms/master/app/auth/*");
|
||||||
|
client.setPublicClient(false);
|
||||||
|
client.setDirectAccessGrantsEnabled(true);
|
||||||
|
client.setAuthenticationFlowBindingOverride(AuthenticationFlowBindings.BROWSER_BINDING, browser.getId());
|
||||||
|
client.setAuthenticationFlowBindingOverride(AuthenticationFlowBindings.DIRECT_GRANT_BINDING, directGrant.getId());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Test
|
||||||
|
public void testRunConsole() throws Exception {
|
||||||
|
Thread.sleep(10000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithClientBrowserOverride() throws Exception {
|
||||||
|
oauth.clientId(TEST_APP_FLOW);
|
||||||
|
String loginFormUrl = oauth.getLoginFormUrl();
|
||||||
|
log.info("loginFormUrl: " + loginFormUrl);
|
||||||
|
|
||||||
|
//Thread.sleep(10000000);
|
||||||
|
|
||||||
|
driver.navigate().to(loginFormUrl);
|
||||||
|
|
||||||
|
Assert.assertEquals("PushTheButton", driver.getTitle());
|
||||||
|
|
||||||
|
// Push the button. I am redirected to username+password form
|
||||||
|
driver.findElement(By.name("submit1")).click();
|
||||||
|
|
||||||
|
|
||||||
|
loginPage.assertCurrent();
|
||||||
|
|
||||||
|
// Fill username+password. I am successfully authenticated
|
||||||
|
oauth.fillLoginForm("test-user@localhost", "password");
|
||||||
|
appPage.assertCurrent();
|
||||||
|
|
||||||
|
events.expectLogin().client("test-app-flow").detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoOverrideBrowser() throws Exception {
|
||||||
|
String clientId = "test-app";
|
||||||
|
testNoOverrideBrowser(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testNoOverrideBrowser(String clientId) {
|
||||||
|
oauth.clientId(clientId);
|
||||||
|
String loginFormUrl = oauth.getLoginFormUrl();
|
||||||
|
log.info("loginFormUrl: " + loginFormUrl);
|
||||||
|
|
||||||
|
//Thread.sleep(10000000);
|
||||||
|
|
||||||
|
driver.navigate().to(loginFormUrl);
|
||||||
|
|
||||||
|
loginPage.assertCurrent();
|
||||||
|
|
||||||
|
// Fill username+password. I am successfully authenticated
|
||||||
|
oauth.fillLoginForm("test-user@localhost", "password");
|
||||||
|
appPage.assertCurrent();
|
||||||
|
|
||||||
|
events.expectLogin().client(clientId).detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGrantAccessTokenNoOverride() throws Exception {
|
||||||
|
testDirectGrantNoOverride("test-app");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testDirectGrantNoOverride(String clientId) {
|
||||||
|
Client httpClient = javax.ws.rs.client.ClientBuilder.newClient();
|
||||||
|
String grantUri = oauth.getResourceOwnerPasswordCredentialGrantUrl();
|
||||||
|
WebTarget grantTarget = httpClient.target(grantUri);
|
||||||
|
|
||||||
|
{ // test no password
|
||||||
|
String header = BasicAuthHelper.createHeader(clientId, "password");
|
||||||
|
Form form = new Form();
|
||||||
|
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||||
|
form.param("username", "test-user@localhost");
|
||||||
|
Response response = grantTarget.request()
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, header)
|
||||||
|
.post(Entity.form(form));
|
||||||
|
assertEquals(401, response.getStatus());
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // test invalid password
|
||||||
|
String header = BasicAuthHelper.createHeader(clientId, "password");
|
||||||
|
Form form = new Form();
|
||||||
|
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||||
|
form.param("username", "test-user@localhost");
|
||||||
|
form.param("password", "invalid");
|
||||||
|
Response response = grantTarget.request()
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, header)
|
||||||
|
.post(Entity.form(form));
|
||||||
|
assertEquals(401, response.getStatus());
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // test valid password
|
||||||
|
String header = BasicAuthHelper.createHeader(clientId, "password");
|
||||||
|
Form form = new Form();
|
||||||
|
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||||
|
form.param("username", "test-user@localhost");
|
||||||
|
form.param("password", "password");
|
||||||
|
Response response = grantTarget.request()
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, header)
|
||||||
|
.post(Entity.form(form));
|
||||||
|
assertEquals(200, response.getStatus());
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.close();
|
||||||
|
events.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGrantAccessTokenWithClientOverride() throws Exception {
|
||||||
|
String clientId = TEST_APP_DIRECT_OVERRIDE;
|
||||||
|
Client httpClient = javax.ws.rs.client.ClientBuilder.newClient();
|
||||||
|
String grantUri = oauth.getResourceOwnerPasswordCredentialGrantUrl();
|
||||||
|
WebTarget grantTarget = httpClient.target(grantUri);
|
||||||
|
|
||||||
|
{ // test no password
|
||||||
|
String header = BasicAuthHelper.createHeader(clientId, "password");
|
||||||
|
Form form = new Form();
|
||||||
|
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||||
|
form.param("username", "test-user@localhost");
|
||||||
|
Response response = grantTarget.request()
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, header)
|
||||||
|
.post(Entity.form(form));
|
||||||
|
assertEquals(200, response.getStatus());
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.close();
|
||||||
|
events.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRestInterface() throws Exception {
|
||||||
|
ClientsResource clients = adminClient.realm("test").clients();
|
||||||
|
List<ClientRepresentation> query = clients.findByClientId(TEST_APP_DIRECT_OVERRIDE);
|
||||||
|
ClientRepresentation clientRep = query.get(0);
|
||||||
|
String directGrantFlowId = clientRep.getAuthenticationFlowBindingOverrides().get(AuthenticationFlowBindings.DIRECT_GRANT_BINDING);
|
||||||
|
Assert.assertNotNull(directGrantFlowId);
|
||||||
|
clientRep.getAuthenticationFlowBindingOverrides().put(AuthenticationFlowBindings.DIRECT_GRANT_BINDING, "");
|
||||||
|
clients.get(clientRep.getId()).update(clientRep);
|
||||||
|
testDirectGrantNoOverride(TEST_APP_DIRECT_OVERRIDE);
|
||||||
|
clientRep.getAuthenticationFlowBindingOverrides().put(AuthenticationFlowBindings.DIRECT_GRANT_BINDING, directGrantFlowId);
|
||||||
|
clients.get(clientRep.getId()).update(clientRep);
|
||||||
|
testGrantAccessTokenWithClientOverride();
|
||||||
|
|
||||||
|
query = clients.findByClientId(TEST_APP_FLOW);
|
||||||
|
clientRep = query.get(0);
|
||||||
|
String browserFlowId = clientRep.getAuthenticationFlowBindingOverrides().get(AuthenticationFlowBindings.BROWSER_BINDING);
|
||||||
|
Assert.assertNotNull(browserFlowId);
|
||||||
|
clientRep.getAuthenticationFlowBindingOverrides().put(AuthenticationFlowBindings.BROWSER_BINDING, "");
|
||||||
|
clients.get(clientRep.getId()).update(clientRep);
|
||||||
|
testNoOverrideBrowser(TEST_APP_FLOW);
|
||||||
|
clientRep.getAuthenticationFlowBindingOverrides().put(AuthenticationFlowBindings.BROWSER_BINDING, browserFlowId);
|
||||||
|
clients.get(clientRep.getId()).update(clientRep);
|
||||||
|
testWithClientBrowserOverride();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue