Merge upstream-master
This commit is contained in:
commit
e44d6f9888
110 changed files with 771 additions and 229 deletions
|
@ -1,10 +1,10 @@
|
||||||
package org.keycloak.broker.provider;
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
import org.keycloak.broker.provider.IdentityProviderMapper;
|
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -35,4 +35,9 @@ public abstract class AbstractIdentityProviderMapper implements IdentityProvider
|
||||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,13 @@
|
||||||
package org.keycloak.broker.provider;
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -152,6 +156,22 @@ public class BrokeredIdentityContext {
|
||||||
this.contextData = contextData;
|
this.contextData = contextData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the attribute, which will be available on "Update profile" page and in authenticators
|
||||||
|
public void setUserAttribute(String attributeName, String attributeValue) {
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
list.add(attributeValue);
|
||||||
|
getContextData().put(Constants.USER_ATTRIBUTES_PREFIX + attributeName, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserAttribute(String attributeName) {
|
||||||
|
List<String> userAttribute = (List<String>) getContextData().get(Constants.USER_ATTRIBUTES_PREFIX + attributeName);
|
||||||
|
if (userAttribute == null || userAttribute.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return userAttribute.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String getFirstName() {
|
public String getFirstName() {
|
||||||
return firstName;
|
return firstName;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.keycloak.broker.provider;
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
@ -26,15 +27,20 @@ public class DefaultDataMarshaller implements IdentityProviderDataMarshaller {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T deserialize(String serialized, Class<T> clazz) {
|
public <T> T deserialize(String serialized, Class<T> clazz) {
|
||||||
|
try {
|
||||||
if (clazz.equals(String.class)) {
|
if (clazz.equals(String.class)) {
|
||||||
return clazz.cast(serialized);
|
return clazz.cast(serialized);
|
||||||
} else {
|
} else {
|
||||||
byte[] bytes = Base64Url.decode(serialized);
|
byte[] bytes = Base64Url.decode(serialized);
|
||||||
try {
|
if (List.class.isAssignableFrom(clazz)) {
|
||||||
|
List list = JsonSerialization.readValue(bytes, List.class);
|
||||||
|
return clazz.cast(list);
|
||||||
|
} else {
|
||||||
return JsonSerialization.readValue(bytes, clazz);
|
return JsonSerialization.readValue(bytes, clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new RuntimeException(ioe);
|
throw new RuntimeException(ioe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,10 +69,10 @@ public class HardcodedAttributeMapper extends AbstractIdentityProviderMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||||
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||||
user.setSingleAttribute(attribute, attributeValue);
|
context.setUserAttribute(attribute, attributeValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -67,7 +67,7 @@ public class HardcodedUserSessionAttributeMapper extends AbstractIdentityProvide
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||||
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||||
context.getClientSession().setUserSessionNote(attribute, attributeValue);
|
context.getClientSession().setUserSessionNote(attribute, attributeValue);
|
||||||
|
|
|
@ -20,8 +20,10 @@ public interface IdentityProviderMapper extends Provider, ProviderFactory<Identi
|
||||||
String getDisplayType();
|
String getDisplayType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to determine what keycloak username and email to use to process the login request from the external IDP
|
* Called to determine what keycloak username and email to use to process the login request from the external IDP.
|
||||||
* Usually used to map BrokeredIdentityContet.username or email.
|
* It's called before "FirstBrokerLogin" flow, so can be used to map attributes to BrokeredIdentityContext ( BrokeredIdentityContext.setUserAttribute ),
|
||||||
|
* which will be available on "Review Profile" page and in authenticators during FirstBrokerLogin flow
|
||||||
|
*
|
||||||
*
|
*
|
||||||
* @param session
|
* @param session
|
||||||
* @param realm
|
* @param realm
|
||||||
|
@ -31,7 +33,7 @@ public interface IdentityProviderMapper extends Provider, ProviderFactory<Identi
|
||||||
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after UserModel is created for first time for this user.
|
* Called after UserModel is created for first time for this user. Called after "FirstBrokerLogin" flow
|
||||||
*
|
*
|
||||||
* @param session
|
* @param session
|
||||||
* @param realm
|
* @param realm
|
||||||
|
|
|
@ -70,7 +70,7 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
||||||
* @param profile to store into context
|
* @param profile to store into context
|
||||||
* @param provider identification of social provider to be used in log dump
|
* @param provider identification of social provider to be used in log dump
|
||||||
*
|
*
|
||||||
* @see #importNewUser(KeycloakSession, RealmModel, UserModel, IdentityProviderMapperModel, BrokeredIdentityContext)
|
* @see #preprocessFederatedIdentity(KeycloakSession, RealmModel, IdentityProviderMapperModel, BrokeredIdentityContext)
|
||||||
* @see BrokeredIdentityContext#getContextData()
|
* @see BrokeredIdentityContext#getContextData()
|
||||||
*/
|
*/
|
||||||
public static void storeUserProfileForMapper(BrokeredIdentityContext user, JsonNode profile, String provider) {
|
public static void storeUserProfileForMapper(BrokeredIdentityContext user, JsonNode profile, String provider) {
|
||||||
|
@ -100,17 +100,17 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String attribute = mapperModel.getConfig().get(CONF_USER_ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(CONF_USER_ATTRIBUTE);
|
||||||
if (attribute == null || attribute.trim().isEmpty()) {
|
if (attribute == null || attribute.trim().isEmpty()) {
|
||||||
logger.debug("Attribute is not configured");
|
logger.warnf("Attribute is not configured for mapper %s", mapperModel.getName());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
attribute = attribute.trim();
|
attribute = attribute.trim();
|
||||||
|
|
||||||
String value = getJsonValue(mapperModel, context);
|
String value = getJsonValue(mapperModel, context);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
user.setSingleAttribute(attribute, value);
|
context.setUserAttribute(attribute, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,13 +123,13 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
||||||
|
|
||||||
String jsonField = mapperModel.getConfig().get(CONF_JSON_FIELD);
|
String jsonField = mapperModel.getConfig().get(CONF_JSON_FIELD);
|
||||||
if (jsonField == null || jsonField.trim().isEmpty()) {
|
if (jsonField == null || jsonField.trim().isEmpty()) {
|
||||||
logger.debug("JSON field path is not configured");
|
logger.warnf("JSON field path is not configured for mapper %s", mapperModel.getName());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
jsonField = jsonField.trim();
|
jsonField = jsonField.trim();
|
||||||
|
|
||||||
if (jsonField.startsWith(JSON_PATH_DELIMITER) || jsonField.endsWith(JSON_PATH_DELIMITER) || jsonField.startsWith("[")) {
|
if (jsonField.startsWith(JSON_PATH_DELIMITER) || jsonField.endsWith(JSON_PATH_DELIMITER) || jsonField.startsWith("[")) {
|
||||||
logger.debug("JSON field path is invalid " + jsonField);
|
logger.warnf("JSON field path is invalid %s", jsonField);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
||||||
String value = getJsonValue(profileJsonNode, jsonField);
|
String value = getJsonValue(profileJsonNode, jsonField);
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
logger.debug("User profile JSON value '" + jsonField + "' is not available.");
|
logger.debugf("User profile JSON value '%s' is not available.", jsonField);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
|
|
@ -70,11 +70,11 @@ public class UserAttributeMapper extends AbstractClaimMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||||
Object value = getClaimValue(mapperModel, context);
|
Object value = getClaimValue(mapperModel, context);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
user.setSingleAttribute(attribute, value.toString());
|
context.setUserAttribute(attribute, value.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,10 +65,6 @@ public class UsernameTemplateMapper extends AbstractClaimMapper {
|
||||||
return "Username Template Importer";
|
return "Username Template Importer";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,11 +80,11 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||||
Object value = getAttribute(mapperModel, context);
|
Object value = getAttribute(mapperModel, context);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
user.setSingleAttribute(attribute, value.toString());
|
context.setUserAttribute(attribute, value.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,11 +71,6 @@ public class UsernameTemplateMapper extends AbstractIdentityProviderMapper {
|
||||||
return "Username Template Importer";
|
return "Username Template Importer";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
|
|
|
@ -23,4 +23,19 @@ public class CollectionUtil {
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return true if all items from col1 are in col2 and viceversa. Order is not taken into account
|
||||||
|
public static <T> boolean collectionEquals(Collection<T> col1, Collection<T> col2) {
|
||||||
|
if (col1.size() != col2.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (T item : col1) {
|
||||||
|
if (!col2.contains(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
|
|
||||||
// Backwards compatibility
|
// Backwards compatibility
|
||||||
if (cacheManager.getCacheConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) == null) {
|
if (cacheManager.getCacheConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) == null) {
|
||||||
logger.warnf("No configuration provided for '%s' cache. Using '%s' configuration as template",
|
logger.debugf("No configuration provided for '%s' cache. Using '%s' configuration as template",
|
||||||
InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||||
|
|
||||||
Configuration sessionCacheConfig = cacheManager.getCacheConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
Configuration sessionCacheConfig = cacheManager.getCacheConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
|
|
||||||
<addColumn tableName="IDENTITY_PROVIDER">
|
<addColumn tableName="IDENTITY_PROVIDER">
|
||||||
<column name="FIRST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
|
<column name="FIRST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
|
||||||
<constraints nullable="false"/>
|
<constraints nullable="true"/>
|
||||||
</column>
|
</column>
|
||||||
</addColumn>
|
</addColumn>
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@
|
||||||
<column name="ACCESS_TOKEN_LIFE_IMPLICIT" type="INT" defaultValueNumeric="0"/>
|
<column name="ACCESS_TOKEN_LIFE_IMPLICIT" type="INT" defaultValueNumeric="0"/>
|
||||||
</addColumn>
|
</addColumn>
|
||||||
|
|
||||||
|
<dropDefaultValue tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD" />
|
||||||
<dropColumn tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD"/>
|
<dropColumn tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD"/>
|
||||||
|
|
||||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP" tableName="KEYCLOAK_GROUP"/>
|
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP" tableName="KEYCLOAK_GROUP"/>
|
||||||
|
@ -83,13 +84,14 @@
|
||||||
<column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
<column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||||
<constraints nullable="false"/>
|
<constraints nullable="false"/>
|
||||||
</column>
|
</column>
|
||||||
<column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
|
<column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||||
<constraints nullable="false"/>
|
<constraints nullable="false"/>
|
||||||
</column>
|
</column>
|
||||||
</addColumn>
|
</addColumn>
|
||||||
|
|
||||||
<update tableName="CLIENT">
|
<update tableName="CLIENT">
|
||||||
<column name="STANDARD_FLOW_ENABLED" valueBoolean="false"/>
|
<column name="STANDARD_FLOW_ENABLED" valueBoolean="false"/>
|
||||||
|
<column name="DIRECT_ACCESS_GRANTS_ENABLED" valueBoolean="true"/>
|
||||||
<where>DIRECT_GRANTS_ONLY = :value</where>
|
<where>DIRECT_GRANTS_ONLY = :value</where>
|
||||||
<whereParams>
|
<whereParams>
|
||||||
<param valueBoolean="true" />
|
<param valueBoolean="true" />
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class Update1_7_0 extends Update {
|
||||||
boolean directGrantsOnly = client.getBoolean("directGrantsOnly", false);
|
boolean directGrantsOnly = client.getBoolean("directGrantsOnly", false);
|
||||||
client.append("standardFlowEnabled", !directGrantsOnly);
|
client.append("standardFlowEnabled", !directGrantsOnly);
|
||||||
client.append("implicitFlowEnabled", false);
|
client.append("implicitFlowEnabled", false);
|
||||||
client.append("directAccessGrantsEnabled", true);
|
client.append("directAccessGrantsEnabled", directGrantsOnly);
|
||||||
client.removeField("directGrantsOnly");
|
client.removeField("directGrantsOnly");
|
||||||
|
|
||||||
clients.save(client);
|
clients.save(client);
|
||||||
|
|
|
@ -27,6 +27,8 @@ public interface OAuth2Constants {
|
||||||
|
|
||||||
String AUTHORIZATION_CODE = "authorization_code";
|
String AUTHORIZATION_CODE = "authorization_code";
|
||||||
|
|
||||||
|
String IMPLICIT = "implicit";
|
||||||
|
|
||||||
String PASSWORD = "password";
|
String PASSWORD = "password";
|
||||||
|
|
||||||
String CLIENT_CREDENTIALS = "client_credentials";
|
String CLIENT_CREDENTIALS = "client_credentials";
|
||||||
|
|
|
@ -30,6 +30,7 @@ public class ClientRepresentation {
|
||||||
protected Boolean implicitFlowEnabled;
|
protected Boolean implicitFlowEnabled;
|
||||||
protected Boolean directAccessGrantsEnabled;
|
protected Boolean directAccessGrantsEnabled;
|
||||||
protected Boolean serviceAccountsEnabled;
|
protected Boolean serviceAccountsEnabled;
|
||||||
|
@Deprecated
|
||||||
protected Boolean directGrantsOnly;
|
protected Boolean directGrantsOnly;
|
||||||
protected Boolean publicClient;
|
protected Boolean publicClient;
|
||||||
protected Boolean frontchannelLogout;
|
protected Boolean frontchannelLogout;
|
||||||
|
@ -216,6 +217,7 @@ public class ClientRepresentation {
|
||||||
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Boolean isDirectGrantsOnly() {
|
public Boolean isDirectGrantsOnly() {
|
||||||
return directGrantsOnly;
|
return directGrantsOnly;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,9 @@ public class OIDCClientRepresentation {
|
||||||
|
|
||||||
private String token_endpoint_auth_method;
|
private String token_endpoint_auth_method;
|
||||||
|
|
||||||
private String grant_types;
|
private List<String> grant_types;
|
||||||
|
|
||||||
private String response_types;
|
private List<String> response_types;
|
||||||
|
|
||||||
private String client_id;
|
private String client_id;
|
||||||
|
|
||||||
|
@ -68,19 +68,19 @@ public class OIDCClientRepresentation {
|
||||||
this.token_endpoint_auth_method = token_endpoint_auth_method;
|
this.token_endpoint_auth_method = token_endpoint_auth_method;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getGrantTypes() {
|
public List<String> getGrantTypes() {
|
||||||
return grant_types;
|
return grant_types;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGrantTypes(String grantTypes) {
|
public void setGrantTypes(List<String> grantTypes) {
|
||||||
this.grant_types = grantTypes;
|
this.grant_types = grantTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getResponseTypes() {
|
public List<String> getResponseTypes() {
|
||||||
return response_types;
|
return response_types;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResponseTypes(String responseTypes) {
|
public void setResponseTypes(List<String> responseTypes) {
|
||||||
this.response_types = responseTypes;
|
this.response_types = responseTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
<module name="org.keycloak.keycloak-core"/>
|
<module name="org.keycloak.keycloak-core"/>
|
||||||
<module name="org.keycloak.keycloak-services"/>
|
<module name="org.keycloak.keycloak-services"/>
|
||||||
<module name="org.keycloak.keycloak-social-core"/>
|
<module name="org.keycloak.keycloak-social-core"/>
|
||||||
<module name="javax.mail.api"/>
|
|
||||||
<module name="javax.ws.rs.api"/>
|
<module name="javax.ws.rs.api"/>
|
||||||
<module name="org.jboss.logging"/>
|
<module name="org.jboss.logging"/>
|
||||||
<module name="org.freemarker"/>
|
<module name="org.freemarker"/>
|
||||||
|
|
|
@ -58,6 +58,7 @@
|
||||||
<module name="org.keycloak.keycloak-wildfly-extensions" services="import"/>
|
<module name="org.keycloak.keycloak-wildfly-extensions" services="import"/>
|
||||||
|
|
||||||
<module name="javax.ws.rs.api"/>
|
<module name="javax.ws.rs.api"/>
|
||||||
|
<module name="javax.mail.api"/>
|
||||||
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
|
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
|
||||||
<module name="org.jboss.resteasy.resteasy-crypto"/>
|
<module name="org.jboss.resteasy.resteasy-crypto"/>
|
||||||
<module name="org.jboss.resteasy.resteasy-multipart-provider"/>
|
<module name="org.jboss.resteasy.resteasy-multipart-provider"/>
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
<module name="org.keycloak.keycloak-core"/>
|
<module name="org.keycloak.keycloak-core"/>
|
||||||
<module name="org.keycloak.keycloak-services"/>
|
<module name="org.keycloak.keycloak-services"/>
|
||||||
<module name="org.keycloak.keycloak-social-core"/>
|
<module name="org.keycloak.keycloak-social-core"/>
|
||||||
<module name="javax.mail.api"/>
|
|
||||||
<module name="javax.ws.rs.api"/>
|
<module name="javax.ws.rs.api"/>
|
||||||
<module name="org.jboss.logging"/>
|
<module name="org.jboss.logging"/>
|
||||||
<module name="org.freemarker"/>
|
<module name="org.freemarker"/>
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
<module name="org.keycloak.keycloak-wildfly-extensions" services="import"/>
|
<module name="org.keycloak.keycloak-wildfly-extensions" services="import"/>
|
||||||
|
|
||||||
<module name="javax.ws.rs.api"/>
|
<module name="javax.ws.rs.api"/>
|
||||||
|
<module name="javax.mail.api"/>
|
||||||
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
|
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
|
||||||
<module name="org.jboss.resteasy.resteasy-multipart-provider"/>
|
<module name="org.jboss.resteasy.resteasy-multipart-provider"/>
|
||||||
<module name="javax.servlet.api"/>
|
<module name="javax.servlet.api"/>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<includes>
|
<includes>
|
||||||
<include>com/google/zxing/**</include>
|
<include>com/google/zxing/**</include>
|
||||||
<include>org/freemarker/**</include>
|
<include>org/freemarker/**</include>
|
||||||
<include>org/jboss/aesh/**</include>
|
<include>org/jboss/aesh/0.65/**</include>
|
||||||
<include>org/keycloak/**</include>
|
<include>org/keycloak/**</include>
|
||||||
<include>org/liquibase/**</include>
|
<include>org/liquibase/**</include>
|
||||||
<include>org/mongodb/**</include>
|
<include>org/mongodb/**</include>
|
||||||
|
|
|
@ -82,11 +82,24 @@
|
||||||
<section>
|
<section>
|
||||||
<title>Migrating to 1.7.0.CR1</title>
|
<title>Migrating to 1.7.0.CR1</title>
|
||||||
<simplesect>
|
<simplesect>
|
||||||
<title>Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator</title>
|
<title>Direct access grants disabled by default for clients</title>
|
||||||
<para>
|
<para>
|
||||||
form-error-page in web.xml will no longer work for client adapter authentication errors. You must define an error-page for
|
In order to add more compliance with OpenID Connect specification, we added flags into admin console to Client Settings page,
|
||||||
the the various HTTP error codes. See documentation for more details if you want to catch and handle adapter error conditions.
|
where you can enable/disable various kinds of OpenID Connect/OAuth2 flows (Standard flow, Implicit flow, Direct Access Grants, Service Accounts).
|
||||||
|
As part of this, we have <literal>Direct Access Grants</literal> (corresponds to OAuth2 <literal>Resource Owner Password Credentials Grant</literal>) disabled by default for new clients.
|
||||||
</para>
|
</para>
|
||||||
|
<para>
|
||||||
|
Clients migrated from previous version have <literal>Direct Access Grants</literal> enabled just if they had flag <literal>Direct Grants Only</literal> on. The
|
||||||
|
<literal>Direct Grants Only</literal> flag was removed as if you enable Direct Access Grants and disable both Standard+Implicit flow, you will achieve same effect.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
We also added builtin client <literal>admin-cli</literal> to each realm. This client has <literal>Direct Access Grants</literal> enabled.
|
||||||
|
So if you're using Admin REST API or Keycloak admin-client, you should update your configuration to use <literal>admin-cli</literal> instead
|
||||||
|
of <literal>security-admin-console</literal> as the latter one doesn't have direct access grants enabled anymore by default.
|
||||||
|
</para>
|
||||||
|
</simplesect>
|
||||||
|
<simplesect>
|
||||||
|
<title>Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator</title>
|
||||||
<para>
|
<para>
|
||||||
In this version, we added <literal>First Broker Login</literal>, which allows you to specify what exactly should be done
|
In this version, we added <literal>First Broker Login</literal>, which allows you to specify what exactly should be done
|
||||||
when new user is logged through Identity provider (or Social provider), but there is no existing Keycloak user
|
when new user is logged through Identity provider (or Social provider), but there is no existing Keycloak user
|
||||||
|
@ -100,6 +113,23 @@
|
||||||
and then you configure the option under <literal>Review Profile</literal> authenticator.
|
and then you configure the option under <literal>Review Profile</literal> authenticator.
|
||||||
</para>
|
</para>
|
||||||
</simplesect>
|
</simplesect>
|
||||||
|
<simplesect>
|
||||||
|
<title>Element 'form-error-page' in web.xml not supported anymore</title>
|
||||||
|
<para>
|
||||||
|
form-error-page in web.xml will no longer work for client adapter authentication errors. You must define an error-page for
|
||||||
|
the various HTTP error codes. See documentation for more details if you want to catch and handle adapter error conditions.
|
||||||
|
</para>
|
||||||
|
</simplesect>
|
||||||
|
<simplesect>
|
||||||
|
<title>IdentityProviderMapper changes</title>
|
||||||
|
<para>
|
||||||
|
There is no change in the interface itself or method signatures. However there is some change in behaviour. We added <literal>First Broker Login</literal> flow
|
||||||
|
in this release and the method <literal>IdentityProviderMapper.importNewUser</literal> is now called after <literal>First Broker Login</literal> flow is finished.
|
||||||
|
So if you want to have any attribute available in <literal>Review Profile</literal> page, you would need to use
|
||||||
|
the method <literal>preprocessFederatedIdentity</literal> instead of <literal>importNewUser</literal> . You can set any attribute by
|
||||||
|
invoke <literal>BrokeredIdentityContext.setUserAttribute</literal> and that will be available on <literal>Review profile</literal> page.
|
||||||
|
</para>
|
||||||
|
</simplesect>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<title>Migrating to 1.6.0.Final</title>
|
<title>Migrating to 1.6.0.Final</title>
|
||||||
|
|
|
@ -96,15 +96,15 @@ keycloak.init({ onLoad: 'login-required' })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button onclick="loadData()">Submit</button>
|
<button onclick="reloadData()">Submit</button>
|
||||||
]]></programlisting>
|
]]></programlisting>
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
The <literal>loadData()</literal> method builds an HTTP request setting the <literal>Authorization</literal>
|
The <literal>loadData()</literal> method builds an HTTP request setting the <literal>Authorization</literal>
|
||||||
header to a bearer token. The <literal>keycloak.token</literal> points to the access token the browser obtained
|
header to a bearer token. The <literal>keycloak.token</literal> points to the access token the browser obtained
|
||||||
when it logged you in. The <literal>loadFailure()</literal> method is invoked on a failure. The <literal>reloadData()</literal>
|
when it logged you in. The <literal>loadFailure()</literal> method is invoked on a failure. The <literal>reloadData()</literal>
|
||||||
function calls <literal>keycloak.onValidAccessToken()</literal> passing in the <literal>loadData()</literal> and
|
function calls <literal>keycloak.updateToken()</literal> passing in the <literal>loadData()</literal> and
|
||||||
<literal>loadFailure()</literal> callbacks. The <literal>keycloak.onValidAcessToken()</literal> method checks to
|
<literal>loadFailure()</literal> callbacks. The <literal>keycloak.updateToken()</literal> method checks to
|
||||||
see if the access token hasn't expired. If it hasn't, and your oauth login returned a refresh token, this method
|
see if the access token hasn't expired. If it hasn't, and your oauth login returned a refresh token, this method
|
||||||
will refresh the access token. Finally, if successful, it will invoke the success callback, which in this case
|
will refresh the access token. Finally, if successful, it will invoke the success callback, which in this case
|
||||||
is the <literal>loadData()</literal> method.
|
is the <literal>loadData()</literal> method.
|
||||||
|
@ -136,6 +136,43 @@ keycloak.updateToken(30).success(function() {
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section id="javascript-implicit-flow">
|
||||||
|
<title>Implicit and Hybrid Flow</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
By default the JavaScript adapter uses <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth">OpenID Connect standard (Authorization code) flow</ulink>, which
|
||||||
|
means that after authentication will Keycloak server redirects back to your application and Javascript adapter will exchange <literal>code</literal> for access token and refresh token.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
However Keycloak also supports <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth">OpenID Connect Implicit flow</ulink>
|
||||||
|
where access token is directly sent after successful authentication from Keycloak and there is no additional request for exchange code.
|
||||||
|
This might have better performance than standard flow as there is no additional request for exchange code-to-token. However sending access token
|
||||||
|
in URL fragment might be security issue in some environments (Token can be more easily stolen from the network etc).
|
||||||
|
</para>
|
||||||
|
<para>To enable implicit flow, you need to enable the flag <literal>Implicit Flow Enabled</literal> for the client in Keycloak admin console. You also need to pass
|
||||||
|
the parameter <literal>flow</literal> with value <literal>implicit</literal> to <literal>init</literal> method, so that Javascript adapter will use implicit flow instead of standard flow.
|
||||||
|
The example is here:
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
keycloak.init({ flow: 'implicit' })
|
||||||
|
]]></programlisting>
|
||||||
|
Note that with implicit flow, you don't have refresh token available after authentication. This makes it harder for your application to periodically update
|
||||||
|
access token in background (without browser redirection). It's recommended that you implement <literal>onTokenExpired</literal> callback method on keycloak object, so you
|
||||||
|
have possibility to do something after token is expired (For example you can call keycloak.login, which will redirect browser to Keycloak login screen and it will immediately
|
||||||
|
redirect you back if SSO session is still valid and user is still logged. However make sure to save the application state before doing redirect.)
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Keycloak also have support for <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth">OpenID Connect Hybrid flow</ulink>. This requires
|
||||||
|
that client in admin console has both flags <literal>Standard Flow Enabled</literal> and <literal>Implicit Flow Enabled</literal> enabled in admin console.
|
||||||
|
The Keycloak will send both the code and tokens to your application. Access token can be immediately used and in the meantime, code can be exchanged for access token and refresh token.
|
||||||
|
Hybrid flow is good for performance similarly like implicit flow, because access token is available immediatelly to your application. But similarly like implicit flow, the token is
|
||||||
|
sent in URL fragment, so security may not be so good.
|
||||||
|
One advantage over implicit flow is, that you have also refresh token available in your application (after code-to-token request is finished in background).
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
For hybrid flow, you need to pass the parameter <literal>flow</literal> with value <literal>hybrid</literal> to <literal>init</literal> method.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<title>Older browsers</title>
|
<title>Older browsers</title>
|
||||||
|
|
||||||
|
@ -178,6 +215,10 @@ new Keycloak({ url: 'http://localhost/auth', realm: 'myrealm', clientId: 'myApp'
|
||||||
<listitem>refreshToken - the base64 encoded token that can be used to retrieve a new token</listitem>
|
<listitem>refreshToken - the base64 encoded token that can be used to retrieve a new token</listitem>
|
||||||
<listitem>refreshTokenParsed - the parsed refresh token</listitem>
|
<listitem>refreshTokenParsed - the parsed refresh token</listitem>
|
||||||
<listitem>timeSkew - estimated skew between local time and Keycloak server in seconds</listitem>
|
<listitem>timeSkew - estimated skew between local time and Keycloak server in seconds</listitem>
|
||||||
|
<listitem>responseMode - responseMode passed during initialization. See below for details. Default value is <literal>fragment</literal></listitem>
|
||||||
|
<listitem>flow - OpenID Connect flow passed during initialization. See <link linkend="javascript-implicit-flow">Implicit flow</link> for details.</listitem>
|
||||||
|
<listitem>responseType - responseType used for send to Keycloak server at login request. This is determined based on the <literal>flow</literal> value used during initialization,
|
||||||
|
but you have possibility to override it by directly set this value</listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -195,6 +236,12 @@ new Keycloak({ url: 'http://localhost/auth', realm: 'myrealm', clientId: 'myApp'
|
||||||
<listitem>refreshToken - set an initial value for the refresh token</listitem>
|
<listitem>refreshToken - set an initial value for the refresh token</listitem>
|
||||||
<listitem>checkLoginIframe - set to enable/disable monitoring login state (default is true)</listitem>
|
<listitem>checkLoginIframe - set to enable/disable monitoring login state (default is true)</listitem>
|
||||||
<listitem>checkLoginIframeInterval - set the interval to check login state (default is 5 seconds)</listitem>
|
<listitem>checkLoginIframeInterval - set the interval to check login state (default is 5 seconds)</listitem>
|
||||||
|
<listitem>responseMode - set the OpenID Connect response mode send to Keycloak server at login request. Valid values are <literal>query</literal> or <literal>fragment</literal> .
|
||||||
|
Default value is <literal>fragment</literal>, which means that after successful authentication will Keycloak redirect to javascript application
|
||||||
|
with OpenID Connect parameters added in URL fragment. This is generally safer and recommended over <literal>query</literal>.
|
||||||
|
</listitem>
|
||||||
|
<listitem>flow - set the OpenID Connect flow. Valid values are <literal>standard</literal>, <literal>implicit</literal> or <literal>hybrid</literal>.
|
||||||
|
See <link linkend="javascript-implicit-flow">Implicit flow</link> for details.</listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
</para>
|
</para>
|
||||||
<para>Returns promise to set functions to be invoked on success or error.</para>
|
<para>Returns promise to set functions to be invoked on success or error.</para>
|
||||||
|
@ -357,6 +404,7 @@ keycloak.onAuthSuccess = function() { alert('authenticated'); }
|
||||||
<listitem>onAuthRefreshSuccess - called when the token is refreshed</listitem>
|
<listitem>onAuthRefreshSuccess - called when the token is refreshed</listitem>
|
||||||
<listitem>onAuthRefreshError - called if there was an error while trying to refresh the token</listitem>
|
<listitem>onAuthRefreshError - called if there was an error while trying to refresh the token</listitem>
|
||||||
<listitem>onAuthLogout - called if the user is logged out (will only be called if the session status iframe is enabled, or in Cordova mode)</listitem>
|
<listitem>onAuthLogout - called if the user is logged out (will only be called if the session status iframe is enabled, or in Cordova mode)</listitem>
|
||||||
|
<listitem>onTokenExpired - called when access token expired. When this happens you can for example refresh token, or if refresh not available (ie. with implicit flow) you can redirect to login screen</listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -44,9 +44,9 @@
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
The <code class="literal">Access Token Lifespan For Implicit Flow</code> is how long an access token is valid for when using OpenID Connect implicit flow.
|
The <code class="literal">Access Token Lifespan For Implicit Flow</code> is how long an access token is valid for when using OpenID Connect implicit flow.
|
||||||
With implicit flow, there is no refresh token available, so that's why the lifespan is usually bigger than default Access Token Lifespan mentioned above.
|
With implicit flow, there is no refresh token available. That's why the lifespan is usually bigger than default Access Token Lifespan mentioned above.
|
||||||
See <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth">OpenID Connect specification</ulink> for details about implicit flow and
|
See <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth">OpenID Connect specification</ulink> for details about implicit flow and
|
||||||
<link linkend="javascript-adapter">Javascript Adapter</link> for some additional details.
|
<link linkend="javascript-adapter">Javascript Adapter</link> for some additional details on how to use it in Keycloak.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
The <literal>Client login timeout</literal> is how long an access code is valid for. An access code is obtained
|
The <literal>Client login timeout</literal> is how long an access code is valid for. An access code is obtained
|
||||||
|
|
|
@ -12,6 +12,7 @@ public interface Details {
|
||||||
String REDIRECT_URI = "redirect_uri";
|
String REDIRECT_URI = "redirect_uri";
|
||||||
String RESPONSE_TYPE = "response_type";
|
String RESPONSE_TYPE = "response_type";
|
||||||
String RESPONSE_MODE = "response_mode";
|
String RESPONSE_MODE = "response_mode";
|
||||||
|
String GRANT_TYPE = "grant_type";
|
||||||
String AUTH_TYPE = "auth_type";
|
String AUTH_TYPE = "auth_type";
|
||||||
String AUTH_METHOD = "auth_method";
|
String AUTH_METHOD = "auth_method";
|
||||||
String IDENTITY_PROVIDER = "identity_provider";
|
String IDENTITY_PROVIDER = "identity_provider";
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
"clients": [
|
"clients": [
|
||||||
{
|
{
|
||||||
"clientId": "examples-admin-client",
|
"clientId": "examples-admin-client",
|
||||||
|
"directAccessGrantsEnabled": true,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"fullScopeAllowed": true,
|
"fullScopeAllowed": true,
|
||||||
"baseUrl": "/examples-admin-client",
|
"baseUrl": "/examples-admin-client",
|
||||||
|
|
|
@ -43,6 +43,8 @@
|
||||||
"clients": [
|
"clients": [
|
||||||
{
|
{
|
||||||
"clientId": "basic-auth-service",
|
"clientId": "basic-auth-service",
|
||||||
|
"standardFlowEnabled": false,
|
||||||
|
"directAccessGrantsEnabled": true,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"adminUrl": "/basicauth",
|
"adminUrl": "/basicauth",
|
||||||
"baseUrl": "/basicauth",
|
"baseUrl": "/basicauth",
|
||||||
|
|
2
examples/broker/saml-broker-authentication/saml-broker-realm.json
Normal file → Executable file
2
examples/broker/saml-broker-authentication/saml-broker-realm.json
Normal file → Executable file
|
@ -40,6 +40,8 @@
|
||||||
"saml.signature.algorithm": "RSA_SHA256",
|
"saml.signature.algorithm": "RSA_SHA256",
|
||||||
"saml.client.signature": "true",
|
"saml.client.signature": "true",
|
||||||
"saml.authnstatement": "true",
|
"saml.authnstatement": "true",
|
||||||
|
"saml_assertion_consumer_url_post": "http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider/endpoint",
|
||||||
|
"saml_single_logout_service_url_post": "http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider/endpoint",
|
||||||
"saml.signing.private.key": "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==",
|
"saml.signing.private.key": "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==",
|
||||||
"saml.signing.certificate": "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin"
|
"saml.signing.certificate": "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin"
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"fullScopeAllowed": true,
|
"fullScopeAllowed": true,
|
||||||
"baseUrl": "/admin-client",
|
"baseUrl": "/admin-client",
|
||||||
|
"directAccessGrantsEnabled": true,
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"/admin-client/*"
|
"/admin-client/*"
|
||||||
],
|
],
|
||||||
|
|
|
@ -178,7 +178,8 @@
|
||||||
"clientId": "admin-client",
|
"clientId": "admin-client",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"publicClient": true,
|
"publicClient": true,
|
||||||
"directGrantsOnly": true
|
"standardFlowEnabled": false,
|
||||||
|
"directAccessGrantsEnabled": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"clientId": "product-sa-client",
|
"clientId": "product-sa-client",
|
||||||
|
|
|
@ -182,7 +182,8 @@
|
||||||
"clientId": "ssh-jmx-admin-client",
|
"clientId": "ssh-jmx-admin-client",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"publicClient": false,
|
"publicClient": false,
|
||||||
"directGrantsOnly": true,
|
"standardFlowEnabled": false,
|
||||||
|
"directAccessGrantsEnabled": true,
|
||||||
"secret": "password"
|
"secret": "password"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -55,6 +55,7 @@ role_read-token=Read token
|
||||||
role_offline-access=Offline access
|
role_offline-access=Offline access
|
||||||
client_account=Account
|
client_account=Account
|
||||||
client_security-admin-console=Security Admin Console
|
client_security-admin-console=Security Admin Console
|
||||||
|
client_admin-cli=Admin CLI
|
||||||
client_realm-management=Realm Management
|
client_realm-management=Realm Management
|
||||||
client_broker=Broker
|
client_broker=Broker
|
||||||
|
|
||||||
|
|
|
@ -865,7 +865,6 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
|
||||||
$scope.client = {
|
$scope.client = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
standardFlowEnabled: true,
|
standardFlowEnabled: true,
|
||||||
directAccessGrantsEnabled: true,
|
|
||||||
attributes: {}
|
attributes: {}
|
||||||
};
|
};
|
||||||
$scope.client.attributes['saml_signature_canonicalization_method'] = $scope.canonicalization[0].value;
|
$scope.client.attributes['saml_signature_canonicalization_method'] = $scope.canonicalization[0].value;
|
||||||
|
|
|
@ -275,7 +275,7 @@
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>{{:: 'idp-sso-relay-state.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'idp-sso-relay-state.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" data-ng-show="!client.bearerOnly && !create && protocol == 'openid-connect'">
|
<div class="form-group" data-ng-show="!client.bearerOnly && !create && protocol == 'openid-connect' && (client.standardFlowEnabled || client.implicitFlowEnabled)">
|
||||||
<label class="col-md-2 control-label" for="newWebOrigin">{{:: 'web-origins' | translate}}</label>
|
<label class="col-md-2 control-label" for="newWebOrigin">{{:: 'web-origins' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
|
|
|
@ -113,6 +113,7 @@ role_read-token=Read token
|
||||||
role_offline-access=Offline access
|
role_offline-access=Offline access
|
||||||
client_account=Account
|
client_account=Account
|
||||||
client_security-admin-console=Security Admin Console
|
client_security-admin-console=Security Admin Console
|
||||||
|
client_admin-cli=Admin CLI
|
||||||
client_realm-management=Realm Management
|
client_realm-management=Realm Management
|
||||||
client_broker=Broker
|
client_broker=Broker
|
||||||
|
|
||||||
|
|
|
@ -54,11 +54,6 @@
|
||||||
<artifactId>freemarker</artifactId>
|
<artifactId>freemarker</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>javax.mail</groupId>
|
|
||||||
<artifactId>mail</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -47,8 +47,9 @@ public class ProfileBean {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.formData = formData;
|
this.formData = formData;
|
||||||
|
|
||||||
if (user.getAttributes() != null) {
|
Map<String, List<String>> modelAttrs = user.getAttributes();
|
||||||
for (Map.Entry<String, List<String>> attr : user.getAttributes().entrySet()) {
|
if (modelAttrs != null) {
|
||||||
|
for (Map.Entry<String, List<String>> attr : modelAttrs.entrySet()) {
|
||||||
List<String> attrValue = attr.getValue();
|
List<String> attrValue = attr.getValue();
|
||||||
if (attrValue != null && attrValue.size() > 0) {
|
if (attrValue != null && attrValue.size() > 0) {
|
||||||
attributes.put(attr.getKey(), attrValue.get(0));
|
attributes.put(attr.getKey(), attrValue.get(0));
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.keycloak.migration;
|
package org.keycloak.migration;
|
||||||
|
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||||
|
|
||||||
|
@ -21,4 +22,6 @@ public interface MigrationProvider extends Provider {
|
||||||
|
|
||||||
List<ProtocolMapperModel> getBuiltinMappers(String protocol);
|
List<ProtocolMapperModel> getBuiltinMappers(String protocol);
|
||||||
|
|
||||||
|
void setupAdminCli(RealmModel realm);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,14 @@ package org.keycloak.migration.migrators;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.migration.MigrationProvider;
|
||||||
import org.keycloak.migration.ModelVersion;
|
import org.keycloak.migration.ModelVersion;
|
||||||
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -17,7 +21,24 @@ public class MigrateTo1_7_0 {
|
||||||
public void migrate(KeycloakSession session) {
|
public void migrate(KeycloakSession session) {
|
||||||
List<RealmModel> realms = session.realms().getRealms();
|
List<RealmModel> realms = session.realms().getRealms();
|
||||||
for (RealmModel realm : realms) {
|
for (RealmModel realm : realms) {
|
||||||
|
// Set default accessToken timeout for implicit flow
|
||||||
realm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT);
|
realm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT);
|
||||||
|
|
||||||
|
// Add 'admin-cli' builtin client
|
||||||
|
MigrationProvider migrationProvider = session.getProvider(MigrationProvider.class);
|
||||||
|
migrationProvider.setupAdminCli(realm);
|
||||||
|
|
||||||
|
// add firstBrokerLogin flow and set it to all identityProviders
|
||||||
|
DefaultAuthenticationFlows.migrateFlows(realm);
|
||||||
|
AuthenticationFlowModel firstBrokerLoginFlow = realm.getFlowByAlias(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW);
|
||||||
|
|
||||||
|
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
||||||
|
for (IdentityProviderModel identityProvider : identityProviders) {
|
||||||
|
if (identityProvider.getFirstBrokerLoginFlowId() == null) {
|
||||||
|
identityProvider.setFirstBrokerLoginFlowId(firstBrokerLoginFlow.getId());
|
||||||
|
realm.updateIdentityProvider(identityProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.keycloak.OAuth2Constants;
|
||||||
*/
|
*/
|
||||||
public interface Constants {
|
public interface Constants {
|
||||||
String ADMIN_CONSOLE_CLIENT_ID = "security-admin-console";
|
String ADMIN_CONSOLE_CLIENT_ID = "security-admin-console";
|
||||||
|
String ADMIN_CLI_CLIENT_ID = "admin-cli";
|
||||||
|
|
||||||
String ACCOUNT_MANAGEMENT_CLIENT_ID = "account";
|
String ACCOUNT_MANAGEMENT_CLIENT_ID = "account";
|
||||||
String IMPERSONATION_SERVICE_CLIENT_ID = "impersonation";
|
String IMPERSONATION_SERVICE_CLIENT_ID = "impersonation";
|
||||||
|
@ -27,4 +28,7 @@ public interface Constants {
|
||||||
|
|
||||||
String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
|
String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
|
||||||
String KEY = "key";
|
String KEY = "key";
|
||||||
|
|
||||||
|
// Prefix for user attributes used in various "context"data maps
|
||||||
|
public static final String USER_ATTRIBUTES_PREFIX = "user.attributes.";
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,6 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
||||||
private boolean implicitFlowEnabled;
|
private boolean implicitFlowEnabled;
|
||||||
private boolean directAccessGrantsEnabled;
|
private boolean directAccessGrantsEnabled;
|
||||||
private boolean serviceAccountsEnabled;
|
private boolean serviceAccountsEnabled;
|
||||||
private boolean directGrantsOnly;
|
|
||||||
private int nodeReRegistrationTimeout;
|
private int nodeReRegistrationTimeout;
|
||||||
|
|
||||||
// We are using names of defaultRoles (not ids)
|
// We are using names of defaultRoles (not ids)
|
||||||
|
@ -278,14 +277,6 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
||||||
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDirectGrantsOnly() {
|
|
||||||
return directGrantsOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDirectGrantsOnly(boolean directGrantsOnly) {
|
|
||||||
this.directGrantsOnly = directGrantsOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getDefaultRoles() {
|
public List<String> getDefaultRoles() {
|
||||||
return defaultRoles;
|
return defaultRoles;
|
||||||
}
|
}
|
||||||
|
|
|
@ -429,6 +429,10 @@ public class DefaultAuthenticationFlows {
|
||||||
if (migrate) {
|
if (migrate) {
|
||||||
// Try to read OTP requirement from browser flow
|
// Try to read OTP requirement from browser flow
|
||||||
AuthenticationFlowModel browserFlow = realm.getBrowserFlow();
|
AuthenticationFlowModel browserFlow = realm.getBrowserFlow();
|
||||||
|
if (browserFlow == null) {
|
||||||
|
browserFlow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
|
||||||
|
}
|
||||||
|
|
||||||
List<AuthenticationExecutionModel> browserExecutions = new LinkedList<>();
|
List<AuthenticationExecutionModel> browserExecutions = new LinkedList<>();
|
||||||
KeycloakModelUtils.deepFindAuthenticationExecutions(realm, browserFlow, browserExecutions);
|
KeycloakModelUtils.deepFindAuthenticationExecutions(realm, browserFlow, browserExecutions);
|
||||||
for (AuthenticationExecutionModel browserExecution : browserExecutions) {
|
for (AuthenticationExecutionModel browserExecution : browserExecutions) {
|
||||||
|
|
|
@ -460,6 +460,10 @@ public class RepresentationToModel {
|
||||||
newRealm.setClientAuthenticationFlow(newRealm.getFlowByAlias(rep.getClientAuthenticationFlow()));
|
newRealm.setClientAuthenticationFlow(newRealm.getFlowByAlias(rep.getClientAuthenticationFlow()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Added in 1.7
|
||||||
|
if (newRealm.getFlowByAlias(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW) == null) {
|
||||||
|
DefaultAuthenticationFlows.firstBrokerLoginFlow(newRealm, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void convertDeprecatedSocialProviders(RealmRepresentation rep) {
|
private static void convertDeprecatedSocialProviders(RealmRepresentation rep) {
|
||||||
|
@ -776,17 +780,19 @@ public class RepresentationToModel {
|
||||||
if (resourceRep.getBaseUrl() != null) client.setBaseUrl(resourceRep.getBaseUrl());
|
if (resourceRep.getBaseUrl() != null) client.setBaseUrl(resourceRep.getBaseUrl());
|
||||||
if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
|
if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
|
||||||
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
|
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());
|
|
||||||
|
|
||||||
// Backwards compatibility only
|
// Backwards compatibility only
|
||||||
if (resourceRep.isDirectGrantsOnly() != null) {
|
if (resourceRep.isDirectGrantsOnly() != null) {
|
||||||
logger.warn("Using deprecated 'directGrantsOnly' configuration in JSON representation. It will be removed in future versions");
|
logger.warn("Using deprecated 'directGrantsOnly' configuration in JSON representation. It will be removed in future versions");
|
||||||
client.setStandardFlowEnabled(!resourceRep.isDirectGrantsOnly());
|
client.setStandardFlowEnabled(!resourceRep.isDirectGrantsOnly());
|
||||||
|
client.setDirectAccessGrantsEnabled(resourceRep.isDirectGrantsOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient());
|
if (resourceRep.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient());
|
||||||
if (resourceRep.isFrontchannelLogout() != null) client.setFrontchannelLogout(resourceRep.isFrontchannelLogout());
|
if (resourceRep.isFrontchannelLogout() != null) client.setFrontchannelLogout(resourceRep.isFrontchannelLogout());
|
||||||
if (resourceRep.getProtocol() != null) client.setProtocol(resourceRep.getProtocol());
|
if (resourceRep.getProtocol() != null) client.setProtocol(resourceRep.getProtocol());
|
||||||
|
|
|
@ -112,23 +112,27 @@ public class InfinispanCacheUserProviderFactory implements CacheUserProviderFact
|
||||||
|
|
||||||
@CacheEntryRemoved
|
@CacheEntryRemoved
|
||||||
public void userRemoved(CacheEntryRemovedEvent<String, CachedUser> event) {
|
public void userRemoved(CacheEntryRemovedEvent<String, CachedUser> event) {
|
||||||
CachedUser user = event.getOldValue();
|
if (event.isPre()) {
|
||||||
if (event.isPre() && user != null) {
|
CachedUser user = event.getValue();
|
||||||
|
if (user != null) {
|
||||||
removeUser(user);
|
removeUser(user);
|
||||||
|
|
||||||
log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
|
log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@CacheEntryInvalidated
|
@CacheEntryInvalidated
|
||||||
public void userInvalidated(CacheEntryInvalidatedEvent<String, CachedUser> event) {
|
public void userInvalidated(CacheEntryInvalidatedEvent<String, CachedUser> event) {
|
||||||
|
if (event.isPre()) {
|
||||||
CachedUser user = event.getValue();
|
CachedUser user = event.getValue();
|
||||||
if (event.isPre() && user != null) {
|
if (user != null) {
|
||||||
removeUser(user);
|
removeUser(user);
|
||||||
|
|
||||||
log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
|
log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@CacheEntriesEvicted
|
@CacheEntriesEvicted
|
||||||
public void userEvicted(CacheEntriesEvictedEvent<String, CachedUser> event) {
|
public void userEvicted(CacheEntriesEvictedEvent<String, CachedUser> event) {
|
||||||
|
|
|
@ -726,7 +726,6 @@ public class RealmAdapter implements RealmModel {
|
||||||
entity.setClientId(clientId);
|
entity.setClientId(clientId);
|
||||||
entity.setEnabled(true);
|
entity.setEnabled(true);
|
||||||
entity.setStandardFlowEnabled(true);
|
entity.setStandardFlowEnabled(true);
|
||||||
entity.setDirectAccessGrantsEnabled(true);
|
|
||||||
entity.setRealm(realm);
|
entity.setRealm(realm);
|
||||||
realm.getClients().add(entity);
|
realm.getClients().add(entity);
|
||||||
em.persist(entity);
|
em.persist(entity);
|
||||||
|
|
|
@ -811,7 +811,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
clientEntity.setRealmId(getId());
|
clientEntity.setRealmId(getId());
|
||||||
clientEntity.setEnabled(true);
|
clientEntity.setEnabled(true);
|
||||||
clientEntity.setStandardFlowEnabled(true);
|
clientEntity.setStandardFlowEnabled(true);
|
||||||
clientEntity.setDirectAccessGrantsEnabled(true);
|
|
||||||
getMongoStore().insertEntity(clientEntity, invocationContext);
|
getMongoStore().insertEntity(clientEntity, invocationContext);
|
||||||
|
|
||||||
final ClientModel model = new ClientAdapter(session, this, clientEntity, invocationContext);
|
final ClientModel model = new ClientAdapter(session, this, clientEntity, invocationContext);
|
||||||
|
|
|
@ -522,7 +522,7 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
logger.debug("finishLogout");
|
logger.debug("finishLogout");
|
||||||
String logoutBindingUri = userSession.getNote(SAML_LOGOUT_BINDING_URI);
|
String logoutBindingUri = userSession.getNote(SAML_LOGOUT_BINDING_URI);
|
||||||
if (logoutBindingUri == null) {
|
if (logoutBindingUri == null) {
|
||||||
logger.error("Can't finish SAML logout as there is no logout binding set");
|
logger.error("Can't finish SAML logout as there is no logout binding set. Please configure the logout service url in the admin console for your client applications.");
|
||||||
return ErrorPage.error(session, Messages.FAILED_LOGOUT);
|
return ErrorPage.error(session, Messages.FAILED_LOGOUT);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter;
|
import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
|
import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
|
||||||
|
import org.keycloak.services.clientregistration.ClientRegistrationException;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
|
|
|
@ -50,6 +50,10 @@
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-email-api</artifactId>
|
<artifactId>keycloak-email-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.mail</groupId>
|
||||||
|
<artifactId>mail</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-login-api</artifactId>
|
<artifactId>keycloak-login-api</artifactId>
|
||||||
|
|
|
@ -41,13 +41,21 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExistingUserInfo duplication = checkExistingUser(context, serializedCtx, brokerContext);
|
String username = getUsername(context, serializedCtx, brokerContext);
|
||||||
|
if (username == null) {
|
||||||
|
logger.warnf("%s is null. Reset flow and enforce showing reviewProfile page", realm.isRegistrationEmailAsUsername() ? "Email" : "Username");
|
||||||
|
context.getClientSession().setNote(ENFORCE_UPDATE_PROFILE, "true");
|
||||||
|
context.resetFlow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExistingUserInfo duplication = checkExistingUser(context, username, serializedCtx, brokerContext);
|
||||||
|
|
||||||
if (duplication == null) {
|
if (duplication == null) {
|
||||||
logger.debugf("No duplication detected. Creating account for user '%s' and linking with identity provider '%s' .",
|
logger.debugf("No duplication detected. Creating account for user '%s' and linking with identity provider '%s' .",
|
||||||
brokerContext.getModelUsername(), brokerContext.getIdpConfig().getAlias());
|
username, brokerContext.getIdpConfig().getAlias());
|
||||||
|
|
||||||
UserModel federatedUser = session.users().addUser(realm, brokerContext.getModelUsername());
|
UserModel federatedUser = session.users().addUser(realm, username);
|
||||||
federatedUser.setEnabled(true);
|
federatedUser.setEnabled(true);
|
||||||
federatedUser.setEmail(brokerContext.getEmail());
|
federatedUser.setEmail(brokerContext.getEmail());
|
||||||
federatedUser.setFirstName(brokerContext.getFirstName());
|
federatedUser.setFirstName(brokerContext.getFirstName());
|
||||||
|
@ -92,7 +100,7 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
|
||||||
}
|
}
|
||||||
|
|
||||||
// Could be overriden to detect duplication based on other criterias (firstName, lastName, ...)
|
// Could be overriden to detect duplication based on other criterias (firstName, lastName, ...)
|
||||||
protected ExistingUserInfo checkExistingUser(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
|
protected ExistingUserInfo checkExistingUser(AuthenticationFlowContext context, String username, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
|
||||||
|
|
||||||
if (brokerContext.getEmail() != null) {
|
if (brokerContext.getEmail() != null) {
|
||||||
UserModel existingUser = context.getSession().users().getUserByEmail(brokerContext.getEmail(), context.getRealm());
|
UserModel existingUser = context.getSession().users().getUserByEmail(brokerContext.getEmail(), context.getRealm());
|
||||||
|
@ -101,7 +109,7 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UserModel existingUser = context.getSession().users().getUserByUsername(brokerContext.getModelUsername(), context.getRealm());
|
UserModel existingUser = context.getSession().users().getUserByUsername(username, context.getRealm());
|
||||||
if (existingUser != null) {
|
if (existingUser != null) {
|
||||||
return new ExistingUserInfo(existingUser.getId(), UserModel.USERNAME, existingUser.getUsername());
|
return new ExistingUserInfo(existingUser.getId(), UserModel.USERNAME, existingUser.getUsername());
|
||||||
}
|
}
|
||||||
|
@ -109,6 +117,11 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String getUsername(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
|
||||||
|
RealmModel realm = context.getRealm();
|
||||||
|
return realm.isRegistrationEmailAsUsername() ? brokerContext.getEmail() : brokerContext.getModelUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean requiresUser() {
|
public boolean requiresUser() {
|
||||||
|
|
|
@ -83,7 +83,7 @@ public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator {
|
||||||
|
|
||||||
RealmModel realm = context.getRealm();
|
RealmModel realm = context.getRealm();
|
||||||
|
|
||||||
List<FormMessage> errors = Validation.validateUpdateProfileForm(true, formData);
|
List<FormMessage> errors = Validation.validateUpdateProfileForm(!realm.isRegistrationEmailAsUsername(), formData);
|
||||||
if (errors != null && !errors.isEmpty()) {
|
if (errors != null && !errors.isEmpty()) {
|
||||||
Response challenge = context.form()
|
Response challenge = context.form()
|
||||||
.setErrors(errors)
|
.setErrors(errors)
|
||||||
|
@ -94,7 +94,8 @@ public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
userCtx.setUsername(formData.getFirst(UserModel.USERNAME));
|
String username = realm.isRegistrationEmailAsUsername() ? formData.getFirst(UserModel.EMAIL) : formData.getFirst(UserModel.USERNAME);
|
||||||
|
userCtx.setUsername(username);
|
||||||
userCtx.setFirstName(formData.getFirst(UserModel.FIRST_NAME));
|
userCtx.setFirstName(formData.getFirst(UserModel.FIRST_NAME));
|
||||||
userCtx.setLastName(formData.getFirst(UserModel.LAST_NAME));
|
userCtx.setLastName(formData.getFirst(UserModel.LAST_NAME));
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,8 @@ public class IdpReviewProfileAuthenticatorFactory implements AuthenticatorFactor
|
||||||
property.setDefaultValue(updateProfileValues);
|
property.setDefaultValue(updateProfileValues);
|
||||||
property.setHelpText("Define conditions under which a user has to review and update his profile after first-time login. Value 'On' means that"
|
property.setHelpText("Define conditions under which a user has to review and update his profile after first-time login. Value 'On' means that"
|
||||||
+ " page for reviewing profile will be displayed and user can review and update his profile. Value 'off' means that page won't be displayed."
|
+ " page for reviewing profile will be displayed and user can review and update his profile. Value 'off' means that page won't be displayed."
|
||||||
+ " Value 'missing' means that page is displayed just when some required attribute is missing (wasn't downloaded from identity provider). Value 'missing' is the default one");
|
+ " Value 'missing' means that page is displayed just when some required attribute is missing (wasn't downloaded from identity provider). Value 'missing' is the default one."
|
||||||
|
+ " WARN: In case that user clicks 'Review profile info' on link duplications page, the update page will be always displayed. You would need to disable this authenticator to never display the page.");
|
||||||
|
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.keycloak.authentication.authenticators.broker.util;
|
package org.keycloak.authentication.authenticators.broker.util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -14,6 +16,7 @@ import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.common.util.reflections.Reflections;
|
import org.keycloak.common.util.reflections.Reflections;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
|
@ -37,13 +40,16 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
|
||||||
private String code;
|
private String code;
|
||||||
private String token;
|
private String token;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private boolean emailAsUsername;
|
||||||
|
|
||||||
private String identityProviderId;
|
private String identityProviderId;
|
||||||
private Map<String, ContextDataEntry> contextData = new HashMap<>();
|
private Map<String, ContextDataEntry> contextData = new HashMap<>();
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
@Override
|
@Override
|
||||||
public boolean isEditUsernameAllowed() {
|
public boolean isEditUsernameAllowed() {
|
||||||
return true;
|
return !emailAsUsername;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
|
@ -159,44 +165,52 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
|
||||||
this.contextData = contextData;
|
this.contextData = contextData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
@Override
|
@Override
|
||||||
public Map<String, List<String>> getAttributes() {
|
public Map<String, List<String>> getAttributes() {
|
||||||
Map<String, List<String>> result = new HashMap<>();
|
Map<String, List<String>> result = new HashMap<>();
|
||||||
|
|
||||||
for (Map.Entry<String, ContextDataEntry> entry : this.contextData.entrySet()) {
|
for (Map.Entry<String, ContextDataEntry> entry : this.contextData.entrySet()) {
|
||||||
if (entry.getKey().startsWith("user.attributes.")) {
|
if (entry.getKey().startsWith(Constants.USER_ATTRIBUTES_PREFIX)) {
|
||||||
ContextDataEntry ctxEntry = entry.getValue();
|
String attrName = entry.getKey().substring(16); // length of USER_ATTRIBUTES_PREFIX
|
||||||
String asString = ctxEntry.getData();
|
List<String> asList = getAttribute(attrName);
|
||||||
try {
|
result.put(attrName, asList);
|
||||||
List<String> asList = JsonSerialization.readValue(asString, List.class);
|
|
||||||
result.put(entry.getKey().substring(16), asList);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new RuntimeException(ioe);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Override
|
||||||
|
public void setSingleAttribute(String name, String value) {
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
list.add(value);
|
||||||
|
setAttribute(name, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
@Override
|
@Override
|
||||||
public void setAttribute(String key, List<String> value) {
|
public void setAttribute(String key, List<String> value) {
|
||||||
try {
|
try {
|
||||||
String listStr = JsonSerialization.writeValueAsString(value);
|
byte[] listBytes = JsonSerialization.writeValueAsBytes(value);
|
||||||
|
String listStr = Base64Url.encode(listBytes);
|
||||||
ContextDataEntry ctxEntry = ContextDataEntry.create(List.class.getName(), listStr);
|
ContextDataEntry ctxEntry = ContextDataEntry.create(List.class.getName(), listStr);
|
||||||
this.contextData.put("user.attributes." + key, ctxEntry);
|
this.contextData.put(Constants.USER_ATTRIBUTES_PREFIX + key, ctxEntry);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new RuntimeException(ioe);
|
throw new RuntimeException(ioe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
@Override
|
@Override
|
||||||
public List<String> getAttribute(String key) {
|
public List<String> getAttribute(String key) {
|
||||||
ContextDataEntry ctxEntry = this.contextData.get("user.attributes." + key);
|
ContextDataEntry ctxEntry = this.contextData.get(Constants.USER_ATTRIBUTES_PREFIX + key);
|
||||||
if (ctxEntry != null) {
|
if (ctxEntry != null) {
|
||||||
try {
|
try {
|
||||||
String asString = ctxEntry.getData();
|
String asString = ctxEntry.getData();
|
||||||
List<String> asList = JsonSerialization.readValue(asString, List.class);
|
byte[] asBytes = Base64Url.decode(asString);
|
||||||
|
List<String> asList = JsonSerialization.readValue(asBytes, List.class);
|
||||||
return asList;
|
return asList;
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new RuntimeException(ioe);
|
throw new RuntimeException(ioe);
|
||||||
|
@ -206,6 +220,17 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Override
|
||||||
|
public String getFirstAttribute(String name) {
|
||||||
|
List<String> attrs = getAttribute(name);
|
||||||
|
if (attrs == null || attrs.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return attrs.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public BrokeredIdentityContext deserialize(KeycloakSession session, ClientSessionModel clientSession) {
|
public BrokeredIdentityContext deserialize(KeycloakSession session, ClientSessionModel clientSession) {
|
||||||
BrokeredIdentityContext ctx = new BrokeredIdentityContext(getId());
|
BrokeredIdentityContext ctx = new BrokeredIdentityContext(getId());
|
||||||
|
|
||||||
|
@ -261,6 +286,8 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
|
||||||
ctx.setToken(context.getToken());
|
ctx.setToken(context.getToken());
|
||||||
ctx.setIdentityProviderId(context.getIdpConfig().getAlias());
|
ctx.setIdentityProviderId(context.getIdpConfig().getAlias());
|
||||||
|
|
||||||
|
ctx.emailAsUsername = context.getClientSession().getRealm().isRegistrationEmailAsUsername();
|
||||||
|
|
||||||
IdentityProviderDataMarshaller serializer = context.getIdp().getMarshaller();
|
IdentityProviderDataMarshaller serializer = context.getIdp().getMarshaller();
|
||||||
|
|
||||||
for (Map.Entry<String, Object> entry : context.getContextData().entrySet()) {
|
for (Map.Entry<String, Object> entry : context.getContextData().entrySet()) {
|
||||||
|
@ -289,7 +316,9 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
return JsonSerialization.readValue(asString, SerializedBrokeredIdentityContext.class);
|
SerializedBrokeredIdentityContext serializedCtx = JsonSerialization.readValue(asString, SerializedBrokeredIdentityContext.class);
|
||||||
|
serializedCtx.emailAsUsername = clientSession.getRealm().isRegistrationEmailAsUsername();
|
||||||
|
return serializedCtx;
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new RuntimeException(ioe);
|
throw new RuntimeException(ioe);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,12 @@ public interface UpdateProfileContext {
|
||||||
|
|
||||||
Map<String, List<String>> getAttributes();
|
Map<String, List<String>> getAttributes();
|
||||||
|
|
||||||
|
void setSingleAttribute(String name, String value);
|
||||||
|
|
||||||
void setAttribute(String key, List<String> value);
|
void setAttribute(String key, List<String> value);
|
||||||
|
|
||||||
|
String getFirstAttribute(String name);
|
||||||
|
|
||||||
List<String> getAttribute(String key);
|
List<String> getAttribute(String key);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,11 +69,21 @@ public class UserUpdateProfileContext implements UpdateProfileContext {
|
||||||
return user.getAttributes();
|
return user.getAttributes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSingleAttribute(String name, String value) {
|
||||||
|
user.setSingleAttribute(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAttribute(String key, List<String> value) {
|
public void setAttribute(String key, List<String> value) {
|
||||||
user.setAttribute(key, value);
|
user.setAttribute(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFirstAttribute(String name) {
|
||||||
|
return user.getFirstAttribute(name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getAttribute(String key) {
|
public List<String> getAttribute(String key) {
|
||||||
return user.getAttribute(key);
|
return user.getAttribute(key);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||||
|
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||||
import org.keycloak.services.clientregistration.ClientRegistrationService;
|
import org.keycloak.services.clientregistration.ClientRegistrationService;
|
||||||
import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory;
|
import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
|
@ -22,13 +23,13 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
||||||
|
|
||||||
public static final List<String> DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED = list("RS256");
|
public static final List<String> DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED = list("RS256");
|
||||||
|
|
||||||
public static final List<String> DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS);
|
public static final List<String> DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS);
|
||||||
|
|
||||||
public static final List<String> DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE);
|
public static final List<String> DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE, OIDCResponseType.NONE, OIDCResponseType.ID_TOKEN, "id_token token", "code id_token", "code token", "code id_token token");
|
||||||
|
|
||||||
public static final List<String> DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public");
|
public static final List<String> DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public");
|
||||||
|
|
||||||
public static final List<String> DEFAULT_RESPONSE_MODES_SUPPORTED = list("query");
|
public static final List<String> DEFAULT_RESPONSE_MODES_SUPPORTED = list("query", "fragment", "form_post");
|
||||||
|
|
||||||
private KeycloakSession session;
|
private KeycloakSession session;
|
||||||
|
|
||||||
|
|
|
@ -178,6 +178,8 @@ public class TokenEndpoint {
|
||||||
} else {
|
} else {
|
||||||
throw new ErrorResponseException(Errors.INVALID_REQUEST, "Invalid " + OIDCLoginProtocol.GRANT_TYPE_PARAM, Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException(Errors.INVALID_REQUEST, "Invalid " + OIDCLoginProtocol.GRANT_TYPE_PARAM, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event.detail(Details.GRANT_TYPE, grantType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response buildAuthorizationCodeAccessTokenResponse() {
|
public Response buildAuthorizationCodeAccessTokenResponse() {
|
||||||
|
@ -223,6 +225,11 @@ public class TokenEndpoint {
|
||||||
throw new ErrorResponseException("invalid_grant", "Auth error", Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException("invalid_grant", "Auth error", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!client.isStandardFlowEnabled()) {
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
throw new ErrorResponseException("invalid_grant", "Client not allowed to exchange code", Response.Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
UserModel user = session.users().getUserById(userSession.getUser().getId(), realm);
|
UserModel user = session.users().getUserById(userSession.getUser().getId(), realm);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
event.error(Errors.USER_NOT_FOUND);
|
event.error(Errors.USER_NOT_FOUND);
|
||||||
|
@ -327,7 +334,12 @@ public class TokenEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response buildResourceOwnerPasswordCredentialsGrant() {
|
public Response buildResourceOwnerPasswordCredentialsGrant() {
|
||||||
event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD);
|
event.detail(Details.AUTH_METHOD, "oauth_credentials");
|
||||||
|
|
||||||
|
if (!client.isDirectAccessGrantsEnabled()) {
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
throw new ErrorResponseException("invalid_grant", "Client not allowed for direct access grants", Response.Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
if (client.isConsentRequired()) {
|
if (client.isConsentRequired()) {
|
||||||
event.error(Errors.CONSENT_DENIED);
|
event.error(Errors.CONSENT_DENIED);
|
||||||
|
@ -393,8 +405,6 @@ public class TokenEndpoint {
|
||||||
throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
|
throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
event.detail(Details.RESPONSE_TYPE, OAuth2Constants.CLIENT_CREDENTIALS);
|
|
||||||
|
|
||||||
UserModel clientUser = session.users().getUserByServiceAccountClient(client);
|
UserModel clientUser = session.users().getUserByServiceAccountClient(client);
|
||||||
|
|
||||||
if (clientUser == null || client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) {
|
if (clientUser == null || client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) {
|
||||||
|
|
|
@ -42,7 +42,7 @@ public abstract class OIDCRedirectUriBuilder {
|
||||||
|
|
||||||
|
|
||||||
// http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
|
// http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
|
||||||
public static class QueryRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
private static class QueryRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
||||||
|
|
||||||
protected QueryRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
|
protected QueryRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
|
||||||
super(uriBuilder);
|
super(uriBuilder);
|
||||||
|
@ -64,7 +64,7 @@ public abstract class OIDCRedirectUriBuilder {
|
||||||
|
|
||||||
|
|
||||||
// http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
|
// http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
|
||||||
public static class FragmentRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
private static class FragmentRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
||||||
|
|
||||||
private StringBuilder fragment;
|
private StringBuilder fragment;
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ public abstract class OIDCRedirectUriBuilder {
|
||||||
|
|
||||||
|
|
||||||
// http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html
|
// http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html
|
||||||
public static class FormPostRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
private static class FormPostRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
||||||
|
|
||||||
private Map<String, String> params = new HashMap<>();
|
private Map<String, String> params = new HashMap<>();
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,16 @@ public class OIDCResponseType {
|
||||||
return new OIDCResponseType(allowedTypes);
|
return new OIDCResponseType(allowedTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static OIDCResponseType parse(List<String> responseTypes) {
|
||||||
|
OIDCResponseType result = new OIDCResponseType(new ArrayList<String>());
|
||||||
|
for (String respType : responseTypes) {
|
||||||
|
OIDCResponseType responseType = parse(respType);
|
||||||
|
result.responseTypes.addAll(responseType.responseTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private static void validateAllowedTypes(List<String> responseTypes) {
|
private static void validateAllowedTypes(List<String> responseTypes) {
|
||||||
if (responseTypes.size() == 0) {
|
if (responseTypes.size() == 0) {
|
||||||
throw new IllegalStateException("No responseType provided");
|
throw new IllegalStateException("No responseType provided");
|
||||||
|
@ -53,9 +63,6 @@ public class OIDCResponseType {
|
||||||
if (responseTypes.contains(NONE) && responseTypes.size() > 1) {
|
if (responseTypes.contains(NONE) && responseTypes.size() > 1) {
|
||||||
throw new IllegalArgumentException("None not allowed with some other response_type");
|
throw new IllegalArgumentException("None not allowed with some other response_type");
|
||||||
}
|
}
|
||||||
if (responseTypes.contains(ID_TOKEN) && responseTypes.size() == 1) {
|
|
||||||
throw new IllegalArgumentException("Not supported to use response_type=id_token alone");
|
|
||||||
}
|
|
||||||
if (responseTypes.contains(TOKEN) && responseTypes.size() == 1) {
|
if (responseTypes.contains(TOKEN) && responseTypes.size() == 1) {
|
||||||
throw new IllegalArgumentException("Not supported to use response_type=token alone");
|
throw new IllegalArgumentException("Not supported to use response_type=token alone");
|
||||||
}
|
}
|
||||||
|
@ -72,7 +79,7 @@ public class OIDCResponseType {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isImplicitFlow() {
|
public boolean isImplicitFlow() {
|
||||||
return hasResponseType(TOKEN) && hasResponseType(ID_TOKEN) && !hasResponseType(CODE);
|
return hasResponseType(ID_TOKEN) && !hasResponseType(CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.keycloak.services.clientregistration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ClientRegistrationException extends RuntimeException {
|
||||||
|
|
||||||
|
public ClientRegistrationException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientRegistrationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientRegistrationException(Throwable throwable) {
|
||||||
|
super(throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientRegistrationException(String message, Throwable throwable) {
|
||||||
|
super(message, throwable);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,48 @@
|
||||||
package org.keycloak.services.clientregistration.oidc;
|
package org.keycloak.services.clientregistration.oidc;
|
||||||
|
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||||
|
import org.keycloak.services.clientregistration.ClientRegistrationException;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class DescriptionConverter {
|
public class DescriptionConverter {
|
||||||
|
|
||||||
public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) {
|
public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) throws ClientRegistrationException {
|
||||||
ClientRepresentation client = new ClientRepresentation();
|
ClientRepresentation client = new ClientRepresentation();
|
||||||
client.setClientId(clientOIDC.getClientId());
|
client.setClientId(clientOIDC.getClientId());
|
||||||
client.setName(clientOIDC.getClientName());
|
client.setName(clientOIDC.getClientName());
|
||||||
client.setRedirectUris(clientOIDC.getRedirectUris());
|
client.setRedirectUris(clientOIDC.getRedirectUris());
|
||||||
client.setBaseUrl(clientOIDC.getClientUri());
|
client.setBaseUrl(clientOIDC.getClientUri());
|
||||||
|
|
||||||
|
List<String> oidcResponseTypes = clientOIDC.getResponseTypes();
|
||||||
|
if (oidcResponseTypes == null || oidcResponseTypes.isEmpty()) {
|
||||||
|
oidcResponseTypes = Collections.singletonList(OIDCResponseType.CODE);
|
||||||
|
}
|
||||||
|
List<String> oidcGrantTypes = clientOIDC.getGrantTypes();
|
||||||
|
|
||||||
|
try {
|
||||||
|
OIDCResponseType responseType = OIDCResponseType.parse(oidcResponseTypes);
|
||||||
|
client.setStandardFlowEnabled(responseType.hasResponseType(OIDCResponseType.CODE));
|
||||||
|
client.setImplicitFlowEnabled(responseType.isImplicitOrHybridFlow());
|
||||||
|
if (oidcGrantTypes != null) {
|
||||||
|
client.setDirectAccessGrantsEnabled(oidcGrantTypes.contains(OAuth2Constants.PASSWORD));
|
||||||
|
client.setServiceAccountsEnabled(oidcGrantTypes.contains(OAuth2Constants.CLIENT_CREDENTIALS));
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new ClientRegistrationException(iae.getMessage(), iae);
|
||||||
|
}
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +55,45 @@ public class DescriptionConverter {
|
||||||
response.setRedirectUris(client.getRedirectUris());
|
response.setRedirectUris(client.getRedirectUris());
|
||||||
response.setRegistrationAccessToken(client.getRegistrationAccessToken());
|
response.setRegistrationAccessToken(client.getRegistrationAccessToken());
|
||||||
response.setRegistrationClientUri(uri.toString());
|
response.setRegistrationClientUri(uri.toString());
|
||||||
|
response.setResponseTypes(getOIDCResponseTypes(client));
|
||||||
|
response.setGrantTypes(getOIDCGrantTypes(client));
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<String> getOIDCResponseTypes(ClientRepresentation client) {
|
||||||
|
List<String> responseTypes = new ArrayList<>();
|
||||||
|
if (client.isStandardFlowEnabled()) {
|
||||||
|
responseTypes.add(OAuth2Constants.CODE);
|
||||||
|
responseTypes.add(OIDCResponseType.NONE);
|
||||||
|
}
|
||||||
|
if (client.isImplicitFlowEnabled()) {
|
||||||
|
responseTypes.add(OIDCResponseType.ID_TOKEN);
|
||||||
|
responseTypes.add("id_token token");
|
||||||
|
}
|
||||||
|
if (client.isStandardFlowEnabled() && client.isImplicitFlowEnabled()) {
|
||||||
|
responseTypes.add("code id_token");
|
||||||
|
responseTypes.add("code token");
|
||||||
|
responseTypes.add("code id_token token");
|
||||||
|
}
|
||||||
|
return responseTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> getOIDCGrantTypes(ClientRepresentation client) {
|
||||||
|
List<String> grantTypes = new ArrayList<>();
|
||||||
|
if (client.isStandardFlowEnabled()) {
|
||||||
|
grantTypes.add(OAuth2Constants.AUTHORIZATION_CODE);
|
||||||
|
}
|
||||||
|
if (client.isImplicitFlowEnabled()) {
|
||||||
|
grantTypes.add(OAuth2Constants.IMPLICIT);
|
||||||
|
}
|
||||||
|
if (client.isDirectAccessGrantsEnabled()) {
|
||||||
|
grantTypes.add(OAuth2Constants.PASSWORD);
|
||||||
|
}
|
||||||
|
if (client.isServiceAccountsEnabled()) {
|
||||||
|
grantTypes.add(OAuth2Constants.CLIENT_CREDENTIALS);
|
||||||
|
}
|
||||||
|
grantTypes.add(OAuth2Constants.REFRESH_TOKEN);
|
||||||
|
return grantTypes;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.services.clientregistration.oidc;
|
package org.keycloak.services.clientregistration.oidc;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -9,6 +10,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
|
import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
|
||||||
import org.keycloak.services.clientregistration.ClientRegistrationAuth;
|
import org.keycloak.services.clientregistration.ClientRegistrationAuth;
|
||||||
|
import org.keycloak.services.clientregistration.ClientRegistrationException;
|
||||||
import org.keycloak.services.clientregistration.ErrorCodes;
|
import org.keycloak.services.clientregistration.ErrorCodes;
|
||||||
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
|
@ -21,6 +23,8 @@ import java.net.URI;
|
||||||
*/
|
*/
|
||||||
public class OIDCClientRegistrationProvider extends AbstractClientRegistrationProvider {
|
public class OIDCClientRegistrationProvider extends AbstractClientRegistrationProvider {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(OIDCClientRegistrationProvider.class);
|
||||||
|
|
||||||
public OIDCClientRegistrationProvider(KeycloakSession session) {
|
public OIDCClientRegistrationProvider(KeycloakSession session) {
|
||||||
super(session);
|
super(session);
|
||||||
}
|
}
|
||||||
|
@ -33,12 +37,17 @@ public class OIDCClientRegistrationProvider extends AbstractClientRegistrationPr
|
||||||
throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier included", Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier included", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
|
ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
|
||||||
client = create(client);
|
client = create(client);
|
||||||
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
|
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
|
||||||
clientOIDC = DescriptionConverter.toExternalResponse(client, uri);
|
clientOIDC = DescriptionConverter.toExternalResponse(client, uri);
|
||||||
clientOIDC.setClientIdIssuedAt(Time.currentTime());
|
clientOIDC.setClientIdIssuedAt(Time.currentTime());
|
||||||
return Response.created(uri).entity(clientOIDC).build();
|
return Response.created(uri).entity(clientOIDC).build();
|
||||||
|
} catch (ClientRegistrationException cre) {
|
||||||
|
log.error(cre.getMessage());
|
||||||
|
throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client metadata invalid", Response.Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -54,11 +63,16 @@ public class OIDCClientRegistrationProvider extends AbstractClientRegistrationPr
|
||||||
@Path("{clientId}")
|
@Path("{clientId}")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public Response updateOIDC(@PathParam("clientId") String clientId, OIDCClientRepresentation clientOIDC) {
|
public Response updateOIDC(@PathParam("clientId") String clientId, OIDCClientRepresentation clientOIDC) {
|
||||||
|
try {
|
||||||
ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
|
ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
|
||||||
client = update(clientId, client);
|
client = update(clientId, client);
|
||||||
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
|
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
|
||||||
clientOIDC = DescriptionConverter.toExternalResponse(client, uri);
|
clientOIDC = DescriptionConverter.toExternalResponse(client, uri);
|
||||||
return Response.ok(clientOIDC).build();
|
return Response.ok(clientOIDC).build();
|
||||||
|
} catch (ClientRegistrationException cre) {
|
||||||
|
log.error(cre.getMessage());
|
||||||
|
throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client metadata invalid", Response.Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
|
|
|
@ -113,6 +113,7 @@ public class RealmManager implements RealmImporter {
|
||||||
setupAccountManagement(realm);
|
setupAccountManagement(realm);
|
||||||
setupBrokerService(realm);
|
setupBrokerService(realm);
|
||||||
setupAdminConsole(realm);
|
setupAdminConsole(realm);
|
||||||
|
setupAdminCli(realm);
|
||||||
setupImpersonationService(realm);
|
setupImpersonationService(realm);
|
||||||
setupAuthenticationFlows(realm);
|
setupAuthenticationFlows(realm);
|
||||||
setupRequiredActions(realm);
|
setupRequiredActions(realm);
|
||||||
|
@ -158,6 +159,30 @@ public class RealmManager implements RealmImporter {
|
||||||
adminConsole.addScopeMapping(adminRole);
|
adminConsole.addScopeMapping(adminRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setupAdminCli(RealmModel realm) {
|
||||||
|
ClientModel adminCli = realm.getClientByClientId(Constants.ADMIN_CLI_CLIENT_ID);
|
||||||
|
if (adminCli == null) {
|
||||||
|
adminCli = new ClientManager(this).createClient(realm, Constants.ADMIN_CLI_CLIENT_ID);
|
||||||
|
adminCli.setName("${client_" + Constants.ADMIN_CLI_CLIENT_ID + "}");
|
||||||
|
adminCli.setEnabled(true);
|
||||||
|
adminCli.setPublicClient(true);
|
||||||
|
adminCli.setFullScopeAllowed(false);
|
||||||
|
adminCli.setStandardFlowEnabled(false);
|
||||||
|
adminCli.setDirectAccessGrantsEnabled(true);
|
||||||
|
|
||||||
|
RoleModel adminRole;
|
||||||
|
if (realm.getName().equals(Config.getAdminRealm())) {
|
||||||
|
adminRole = realm.getRole(AdminRoles.ADMIN);
|
||||||
|
} else {
|
||||||
|
String realmAdminApplicationClientId = getRealmAdminClientId(realm);
|
||||||
|
ClientModel realmAdminApp = realm.getClientByClientId(realmAdminApplicationClientId);
|
||||||
|
adminRole = realmAdminApp.getRole(AdminRoles.REALM_ADMIN);
|
||||||
|
}
|
||||||
|
adminCli.addScopeMapping(adminRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public String getRealmAdminClientId(RealmModel realm) {
|
public String getRealmAdminClientId(RealmModel realm) {
|
||||||
return Constants.REALM_MANAGEMENT_CLIENT_ID;
|
return Constants.REALM_MANAGEMENT_CLIENT_ID;
|
||||||
}
|
}
|
||||||
|
@ -375,6 +400,16 @@ public class RealmManager implements RealmImporter {
|
||||||
|
|
||||||
if (!hasBrokerClient(rep)) setupBrokerService(realm);
|
if (!hasBrokerClient(rep)) setupBrokerService(realm);
|
||||||
if (!hasAdminConsoleClient(rep)) setupAdminConsole(realm);
|
if (!hasAdminConsoleClient(rep)) setupAdminConsole(realm);
|
||||||
|
|
||||||
|
boolean postponeAdminCliSetup = false;
|
||||||
|
if (!hasAdminCliClient(rep)) {
|
||||||
|
if (hasRealmAdminManagementClient(rep)) {
|
||||||
|
postponeAdminCliSetup = true;
|
||||||
|
} else {
|
||||||
|
setupAdminCli(realm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasRealmRole(rep, Constants.OFFLINE_ACCESS_ROLE)) setupOfflineTokens(realm);
|
if (!hasRealmRole(rep, Constants.OFFLINE_ACCESS_ROLE)) setupOfflineTokens(realm);
|
||||||
|
|
||||||
RepresentationToModel.importRealm(session, rep, realm);
|
RepresentationToModel.importRealm(session, rep, realm);
|
||||||
|
@ -389,6 +424,10 @@ public class RealmManager implements RealmImporter {
|
||||||
setupImpersonationService(realm);
|
setupImpersonationService(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (postponeAdminCliSetup) {
|
||||||
|
setupAdminCli(realm);
|
||||||
|
}
|
||||||
|
|
||||||
setupAuthenticationFlows(realm);
|
setupAuthenticationFlows(realm);
|
||||||
setupRequiredActions(realm);
|
setupRequiredActions(realm);
|
||||||
|
|
||||||
|
@ -428,6 +467,10 @@ public class RealmManager implements RealmImporter {
|
||||||
return hasClient(rep, Constants.ADMIN_CONSOLE_CLIENT_ID);
|
return hasClient(rep, Constants.ADMIN_CONSOLE_CLIENT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasAdminCliClient(RealmRepresentation rep) {
|
||||||
|
return hasClient(rep, Constants.ADMIN_CLI_CLIENT_ID);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasClient(RealmRepresentation rep, String clientId) {
|
private boolean hasClient(RealmRepresentation rep, String clientId) {
|
||||||
if (rep.getClients() != null) {
|
if (rep.getClients() != null) {
|
||||||
for (ClientRepresentation clientRep : rep.getClients()) {
|
for (ClientRepresentation clientRep : rep.getClients()) {
|
||||||
|
|
|
@ -10,12 +10,14 @@ import org.keycloak.migration.MigrationProvider;
|
||||||
import org.keycloak.models.ClaimMask;
|
import org.keycloak.models.ClaimMask;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
import org.keycloak.protocol.LoginProtocolFactory;
|
import org.keycloak.protocol.LoginProtocolFactory;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||||
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Various common utils needed for migration from older version to newer
|
* Various common utils needed for migration from older version to newer
|
||||||
|
@ -59,6 +61,11 @@ public class DefaultMigrationProvider implements MigrationProvider {
|
||||||
return providerFactory.getBuiltinMappers();
|
return providerFactory.getBuiltinMappers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupAdminCli(RealmModel realm) {
|
||||||
|
new RealmManager(session).setupAdminCli(realm);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import java.util.List;
|
||||||
|
|
||||||
import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
|
import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
|
||||||
import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext;
|
import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
@ -29,11 +30,12 @@ public class AttributeFormDataProcessor {
|
||||||
|
|
||||||
public static void process(MultivaluedMap<String, String> formData, RealmModel realm, UpdateProfileContext user) {
|
public static void process(MultivaluedMap<String, String> formData, RealmModel realm, UpdateProfileContext user) {
|
||||||
for (String key : formData.keySet()) {
|
for (String key : formData.keySet()) {
|
||||||
if (!key.startsWith("user.attributes.")) continue;
|
if (!key.startsWith(Constants.USER_ATTRIBUTES_PREFIX)) continue;
|
||||||
String attribute = key.substring("user.attributes.".length());
|
String attribute = key.substring(Constants.USER_ATTRIBUTES_PREFIX.length());
|
||||||
|
|
||||||
// Need to handle case when attribute has multiple values, but in UI was displayed just first value
|
// Need to handle case when attribute has multiple values, but in UI was displayed just first value
|
||||||
List<String> modelValue = new ArrayList<>(user.getAttribute(attribute));
|
List<String> modelVal = user.getAttribute(attribute);
|
||||||
|
List<String> modelValue = modelVal==null ? new ArrayList<String>() : new ArrayList<>(modelVal);
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (String value : formData.get(key)) {
|
for (String value : formData.get(key)) {
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.GroupRepresentation;
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.services.clientregistration.ClientRegistrationException;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.LDAPConnectionTestManager;
|
import org.keycloak.services.managers.LDAPConnectionTestManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package org.keycloak.test;
|
package org.keycloak.test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||||
|
@ -16,7 +19,7 @@ public class ResponseTypeTest {
|
||||||
assertFail("foo");
|
assertFail("foo");
|
||||||
assertSuccess("code");
|
assertSuccess("code");
|
||||||
assertSuccess("none");
|
assertSuccess("none");
|
||||||
assertFail("id_token");
|
assertSuccess("id_token");
|
||||||
assertFail("token");
|
assertFail("token");
|
||||||
assertFail("refresh_token");
|
assertFail("refresh_token");
|
||||||
assertSuccess("id_token token");
|
assertSuccess("id_token token");
|
||||||
|
@ -27,6 +30,38 @@ public class ResponseTypeTest {
|
||||||
assertFail("code refresh_token");
|
assertFail("code refresh_token");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleResponseTypes() {
|
||||||
|
try {
|
||||||
|
OIDCResponseType.parse(Arrays.asList("code", "token"));
|
||||||
|
Assert.fail("Not expected to parse with success");
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
}
|
||||||
|
|
||||||
|
OIDCResponseType responseType = OIDCResponseType.parse(Collections.singletonList("code"));
|
||||||
|
Assert.assertTrue(responseType.hasResponseType("code"));
|
||||||
|
Assert.assertFalse(responseType.hasResponseType("none"));
|
||||||
|
Assert.assertFalse(responseType.isImplicitOrHybridFlow());
|
||||||
|
|
||||||
|
responseType = OIDCResponseType.parse(Arrays.asList("code", "none"));
|
||||||
|
Assert.assertTrue(responseType.hasResponseType("code"));
|
||||||
|
Assert.assertTrue(responseType.hasResponseType("none"));
|
||||||
|
Assert.assertFalse(responseType.isImplicitOrHybridFlow());
|
||||||
|
|
||||||
|
responseType = OIDCResponseType.parse(Arrays.asList("code", "code token"));
|
||||||
|
Assert.assertTrue(responseType.hasResponseType("code"));
|
||||||
|
Assert.assertFalse(responseType.hasResponseType("none"));
|
||||||
|
Assert.assertTrue(responseType.hasResponseType("token"));
|
||||||
|
Assert.assertFalse(responseType.hasResponseType("id_token"));
|
||||||
|
Assert.assertTrue(responseType.isImplicitOrHybridFlow());
|
||||||
|
Assert.assertFalse(responseType.isImplicitFlow());
|
||||||
|
|
||||||
|
responseType = OIDCResponseType.parse(Arrays.asList("id_token", "id_token token"));
|
||||||
|
Assert.assertFalse(responseType.hasResponseType("code"));
|
||||||
|
Assert.assertTrue(responseType.isImplicitOrHybridFlow());
|
||||||
|
Assert.assertTrue(responseType.isImplicitFlow());
|
||||||
|
}
|
||||||
|
|
||||||
private void assertSuccess(String responseType) {
|
private void assertSuccess(String responseType) {
|
||||||
OIDCResponseType.parse(responseType);
|
OIDCResponseType.parse(responseType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,7 +183,7 @@ public class ContainersTestEnricher {
|
||||||
private void initializeAdminClient() {
|
private void initializeAdminClient() {
|
||||||
adminClient.set(Keycloak.getInstance(
|
adminClient.set(Keycloak.getInstance(
|
||||||
getAuthServerContextRootFromSystemProperty() + "/auth",
|
getAuthServerContextRootFromSystemProperty() + "/auth",
|
||||||
MASTER, ADMIN, ADMIN, Constants.ADMIN_CONSOLE_CLIENT_ID));
|
MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeOAuthClient() {
|
private void initializeOAuthClient() {
|
||||||
|
|
|
@ -104,7 +104,7 @@ public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTes
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getToken(String username, String password) {
|
private String getToken(String username, String password) {
|
||||||
return oauthClient.getToken(REALM_NAME, "security-admin-console", null, username, password).getToken();
|
return oauthClient.getToken(REALM_NAME, Constants.ADMIN_CLI_CLIENT_ID, null, username, password).getToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,16 @@ package org.keycloak.testsuite.client;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.client.registration.Auth;
|
import org.keycloak.client.registration.Auth;
|
||||||
import org.keycloak.client.registration.ClientRegistrationException;
|
import org.keycloak.client.registration.ClientRegistrationException;
|
||||||
|
import org.keycloak.common.util.CollectionUtil;
|
||||||
|
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||||
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
||||||
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
||||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
@ -49,6 +53,8 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
||||||
assertEquals("http://root", response.getClientUri());
|
assertEquals("http://root", response.getClientUri());
|
||||||
assertEquals(1, response.getRedirectUris().size());
|
assertEquals(1, response.getRedirectUris().size());
|
||||||
assertEquals("http://redirect", response.getRedirectUris().get(0));
|
assertEquals("http://redirect", response.getRedirectUris().get(0));
|
||||||
|
assertEquals(Arrays.asList("code", "none"), response.getResponseTypes());
|
||||||
|
assertEquals(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN), response.getGrantTypes());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -59,6 +65,8 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
||||||
OIDCClientRepresentation rep = reg.oidc().get(response.getClientId());
|
OIDCClientRepresentation rep = reg.oidc().get(response.getClientId());
|
||||||
assertNotNull(rep);
|
assertNotNull(rep);
|
||||||
assertNotEquals(response.getRegistrationAccessToken(), rep.getRegistrationAccessToken());
|
assertNotEquals(response.getRegistrationAccessToken(), rep.getRegistrationAccessToken());
|
||||||
|
assertTrue(CollectionUtil.collectionEquals(Arrays.asList("code", "none"), response.getResponseTypes()));
|
||||||
|
assertTrue(CollectionUtil.collectionEquals(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN), response.getGrantTypes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -67,11 +75,26 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
||||||
reg.auth(Auth.token(response));
|
reg.auth(Auth.token(response));
|
||||||
|
|
||||||
response.setRedirectUris(Collections.singletonList("http://newredirect"));
|
response.setRedirectUris(Collections.singletonList("http://newredirect"));
|
||||||
|
response.setResponseTypes(Arrays.asList("code", "id_token token", "code id_token token"));
|
||||||
|
response.setGrantTypes(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD));
|
||||||
|
|
||||||
OIDCClientRepresentation updated = reg.oidc().update(response);
|
OIDCClientRepresentation updated = reg.oidc().update(response);
|
||||||
|
|
||||||
assertEquals(1, updated.getRedirectUris().size());
|
assertTrue(CollectionUtil.collectionEquals(Collections.singletonList("http://newredirect"), updated.getRedirectUris()));
|
||||||
assertEquals("http://newredirect", updated.getRedirectUris().get(0));
|
assertTrue(CollectionUtil.collectionEquals(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD), updated.getGrantTypes()));
|
||||||
|
assertTrue(CollectionUtil.collectionEquals(Arrays.asList(OAuth2Constants.CODE, OIDCResponseType.NONE, OIDCResponseType.ID_TOKEN, "id_token token", "code id_token", "code token", "code id_token token"), updated.getResponseTypes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateClientError() throws ClientRegistrationException {
|
||||||
|
try {
|
||||||
|
OIDCClientRepresentation response = create();
|
||||||
|
reg.auth(Auth.token(response));
|
||||||
|
response.setResponseTypes(Arrays.asList("code", "token"));
|
||||||
|
reg.oidc().update(response);
|
||||||
|
fail("Not expected to end with success");
|
||||||
|
} catch (ClientRegistrationException cre) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -135,7 +135,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
|
||||||
return expect(EventType.CLIENT_LOGIN)
|
return expect(EventType.CLIENT_LOGIN)
|
||||||
.detail(Details.CODE_ID, isCodeId())
|
.detail(Details.CODE_ID, isCodeId())
|
||||||
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
|
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
|
||||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)
|
.detail(Details.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)
|
||||||
.removeDetail(Details.CODE_ID)
|
.removeDetail(Details.CODE_ID)
|
||||||
.session(isUUID());
|
.session(isUUID());
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,7 +187,7 @@ public class AdapterTestStrategy extends ExternalResource {
|
||||||
Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
|
Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
|
||||||
|
|
||||||
// View stats
|
// View stats
|
||||||
List<Map<String, String>> stats = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", "security-admin-console").realm("demo").getClientSessionStats();
|
List<Map<String, String>> stats = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID).realm("demo").getClientSessionStats();
|
||||||
Map<String, String> customerPortalStats = null;
|
Map<String, String> customerPortalStats = null;
|
||||||
Map<String, String> productPortalStats = null;
|
Map<String, String> productPortalStats = null;
|
||||||
for (Map<String, String> s : stats) {
|
for (Map<String, String> s : stats) {
|
||||||
|
@ -594,7 +594,7 @@ public class AdapterTestStrategy extends ExternalResource {
|
||||||
loginAndCheckSession(driver, loginPage);
|
loginAndCheckSession(driver, loginPage);
|
||||||
|
|
||||||
// logout mposolda with admin client
|
// logout mposolda with admin client
|
||||||
Keycloak keycloakAdmin = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID);
|
Keycloak keycloakAdmin = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID);
|
||||||
ApiUtil.findClientByClientId(keycloakAdmin.realm("demo"), "session-portal").logoutUser("mposolda");
|
ApiUtil.findClientByClientId(keycloakAdmin.realm("demo"), "session-portal").logoutUser("mposolda");
|
||||||
|
|
||||||
// bburke should be still logged with original httpSession in our browser window
|
// bburke should be still logged with original httpSession in our browser window
|
||||||
|
|
|
@ -131,7 +131,7 @@ public class RelativeUriAdapterTest {
|
||||||
Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
|
Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
|
||||||
|
|
||||||
// View stats
|
// View stats
|
||||||
List<Map<String, String>> stats = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", "security-admin-console").realm("demo").getClientSessionStats();
|
List<Map<String, String>> stats = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID).realm("demo").getClientSessionStats();
|
||||||
Map<String, String> customerPortalStats = null;
|
Map<String, String> customerPortalStats = null;
|
||||||
Map<String, String> productPortalStats = null;
|
Map<String, String> productPortalStats = null;
|
||||||
for (Map<String, String> s : stats) {
|
for (Map<String, String> s : stats) {
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class AddUserTest {
|
||||||
try {
|
try {
|
||||||
server.start();
|
server.start();
|
||||||
|
|
||||||
Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "addusertest-admin", "password", Constants.ADMIN_CONSOLE_CLIENT_ID);
|
Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "addusertest-admin", "password", Constants.ADMIN_CLI_CLIENT_ID);
|
||||||
keycloak.realms().findAll();
|
keycloak.realms().findAll();
|
||||||
|
|
||||||
RealmRepresentation testRealm = new RealmRepresentation();
|
RealmRepresentation testRealm = new RealmRepresentation();
|
||||||
|
|
|
@ -45,10 +45,12 @@ public abstract class AbstractClientTest {
|
||||||
testRealm.setEnabled(true);
|
testRealm.setEnabled(true);
|
||||||
testRealm.setAccessCodeLifespanUserAction(600);
|
testRealm.setAccessCodeLifespanUserAction(600);
|
||||||
KeycloakModelUtils.generateRealmKeys(testRealm);
|
KeycloakModelUtils.generateRealmKeys(testRealm);
|
||||||
|
|
||||||
|
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID);
|
keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID);
|
||||||
realm = keycloak.realm(REALM_NAME);
|
realm = keycloak.realm(REALM_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ public class AdminAPITest {
|
||||||
RealmManager manager = new RealmManager(session);
|
RealmManager manager = new RealmManager(session);
|
||||||
|
|
||||||
RealmModel adminRealm = manager.getRealm(Config.getAdminRealm());
|
RealmModel adminRealm = manager.getRealm(Config.getAdminRealm());
|
||||||
ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
|
ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CLI_CLIENT_ID);
|
||||||
TokenManager tm = new TokenManager();
|
TokenManager tm = new TokenManager();
|
||||||
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
|
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
import org.keycloak.admin.client.resource.ProtocolMappersResource;
|
import org.keycloak.admin.client.resource.ProtocolMappersResource;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||||
|
@ -42,7 +43,7 @@ public class ClientTest extends AbstractClientTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getClients() {
|
public void getClients() {
|
||||||
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker");
|
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", Constants.ADMIN_CLI_CLIENT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createClient() {
|
private String createClient() {
|
||||||
|
@ -60,7 +61,7 @@ public class ClientTest extends AbstractClientTest {
|
||||||
String id = createClient();
|
String id = createClient();
|
||||||
|
|
||||||
assertNotNull(realm.clients().get(id));
|
assertNotNull(realm.clients().get(id));
|
||||||
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "my-app");
|
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "my-app", Constants.ADMIN_CLI_CLIENT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -117,7 +117,7 @@ public class ImpersonationTest {
|
||||||
RealmManager manager = new RealmManager(session);
|
RealmManager manager = new RealmManager(session);
|
||||||
|
|
||||||
RealmModel adminRealm = manager.getRealm(realm);
|
RealmModel adminRealm = manager.getRealm(realm);
|
||||||
ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
|
ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CLI_CLIENT_ID);
|
||||||
TokenManager tm = new TokenManager();
|
TokenManager tm = new TokenManager();
|
||||||
UserModel admin = session.users().getUserByUsername(username, adminRealm);
|
UserModel admin = session.users().getUserByUsername(username, adminRealm);
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
|
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
|
||||||
|
|
|
@ -178,6 +178,48 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test user registers with IdentityProvider with emailAsUsername
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRegistrationWithEmailAsUsername() {
|
||||||
|
// Require updatePassword after user registered with broker
|
||||||
|
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
|
||||||
|
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_ON);
|
||||||
|
realmWithBroker.setRegistrationEmailAsUsername(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, APP_REALM_ID);
|
||||||
|
|
||||||
|
loginIDP("pedroigor");
|
||||||
|
this.updateProfileWithUsernamePage.assertCurrent();
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.updateProfileWithUsernamePage.update("Test", "User", "some-user@redhat.com", "some-user");
|
||||||
|
Assert.fail("It is not expected to see username field");
|
||||||
|
} catch (NoSuchElementException expected) {
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateProfileWithUsernamePage.update("Test", "User", "some-user@redhat.com");
|
||||||
|
|
||||||
|
// assert authenticated
|
||||||
|
assertFederatedUser("some-user@redhat.com", "some-user@redhat.com", "pedroigor");
|
||||||
|
|
||||||
|
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
|
||||||
|
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_MISSING);
|
||||||
|
realmWithBroker.setRegistrationEmailAsUsername(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, APP_REALM_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests that duplication is detected, the confirmation page is displayed, user clicks on "Review profile" and goes back to updateProfile page and resolves duplication
|
* Tests that duplication is detected, the confirmation page is displayed, user clicks on "Review profile" and goes back to updateProfile page and resolves duplication
|
||||||
* by create new user
|
* by create new user
|
||||||
|
|
|
@ -266,48 +266,6 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided() {
|
|
||||||
|
|
||||||
getRealm().setRegistrationEmailAsUsername(true);
|
|
||||||
brokerServerRule.stopSession(this.session, true);
|
|
||||||
this.session = brokerServerRule.startSession();
|
|
||||||
|
|
||||||
try {
|
|
||||||
IdentityProviderModel identityProviderModel = getIdentityProviderModel();
|
|
||||||
setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
|
|
||||||
|
|
||||||
authenticateWithIdentityProvider(identityProviderModel, "test-user-noemail", false);
|
|
||||||
|
|
||||||
brokerServerRule.stopSession(session, true);
|
|
||||||
session = brokerServerRule.startSession();
|
|
||||||
|
|
||||||
// check correct user is created with username from provider as email is not available
|
|
||||||
RealmModel realm = getRealm();
|
|
||||||
UserModel federatedUser = getFederatedUser();
|
|
||||||
assertNotNull(federatedUser);
|
|
||||||
|
|
||||||
doAssertFederatedUserNoEmail(federatedUser);
|
|
||||||
|
|
||||||
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
|
|
||||||
|
|
||||||
assertEquals(1, federatedIdentities.size());
|
|
||||||
|
|
||||||
FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
|
|
||||||
|
|
||||||
assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
|
|
||||||
revokeGrant();
|
|
||||||
|
|
||||||
driver.navigate().to("http://localhost:8081/test-app/logout");
|
|
||||||
driver.navigate().to("http://localhost:8081/test-app");
|
|
||||||
|
|
||||||
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
getRealm().setRegistrationEmailAsUsername(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDisabled() {
|
public void testDisabled() {
|
||||||
IdentityProviderModel identityProviderModel = getIdentityProviderModel();
|
IdentityProviderModel identityProviderModel = getIdentityProviderModel();
|
||||||
|
@ -395,6 +353,8 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
|
||||||
public void testAccountManagementLinkedIdentityAlreadyExists() {
|
public void testAccountManagementLinkedIdentityAlreadyExists() {
|
||||||
// Login as "test-user" through broker
|
// Login as "test-user" through broker
|
||||||
IdentityProviderModel identityProvider = getIdentityProviderModel();
|
IdentityProviderModel identityProvider = getIdentityProviderModel();
|
||||||
|
setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
|
||||||
|
|
||||||
assertSuccessfulAuthentication(identityProvider, "test-user", "test-user@localhost", false);
|
assertSuccessfulAuthentication(identityProvider, "test-user", "test-user@localhost", false);
|
||||||
|
|
||||||
// Login as pedroigor to account management
|
// Login as pedroigor to account management
|
||||||
|
@ -481,6 +441,9 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
|
||||||
|
|
||||||
authenticateWithIdentityProvider(identityProviderModel, "test-user", true);
|
authenticateWithIdentityProvider(identityProviderModel, "test-user", true);
|
||||||
|
|
||||||
|
brokerServerRule.stopSession(session, true);
|
||||||
|
session = brokerServerRule.startSession();
|
||||||
|
|
||||||
UserModel federatedUser = getFederatedUser();
|
UserModel federatedUser = getFederatedUser();
|
||||||
RealmModel realm = getRealm();
|
RealmModel realm = getRealm();
|
||||||
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
|
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
|
||||||
|
|
|
@ -115,11 +115,6 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
|
||||||
super.testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername();
|
super.testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided() {
|
|
||||||
super.testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTokenStorageAndRetrievalByApplication() {
|
public void testTokenStorageAndRetrievalByApplication() {
|
||||||
super.testTokenStorageAndRetrievalByApplication();
|
super.testTokenStorageAndRetrievalByApplication();
|
||||||
|
|
|
@ -77,6 +77,8 @@ public class FederationProvidersIntegrationTest {
|
||||||
ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1");
|
ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1");
|
||||||
|
|
||||||
LDAPObject existing = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678");
|
LDAPObject existing = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678");
|
||||||
|
|
||||||
|
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,8 @@ public class BruteForceTest {
|
||||||
|
|
||||||
appRealm.setBruteForceProtected(true);
|
appRealm.setBruteForceProtected(true);
|
||||||
appRealm.setFailureFactor(2);
|
appRealm.setFailureFactor(2);
|
||||||
|
|
||||||
|
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -116,7 +118,7 @@ public class BruteForceTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAdminToken() throws Exception {
|
public String getAdminToken() throws Exception {
|
||||||
String clientId = Constants.ADMIN_CONSOLE_CLIENT_ID;
|
String clientId = Constants.ADMIN_CLI_CLIENT_ID;
|
||||||
return oauth.doGrantAccessTokenRequest("master", "admin", "admin", null, clientId, null).getAccessToken();
|
return oauth.doGrantAccessTokenRequest("master", "admin", "admin", null, clientId, null).getAccessToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -127,7 +127,11 @@ public class CustomFlowTest {
|
||||||
// Set passthrough clientAuthenticator for our clients
|
// Set passthrough clientAuthenticator for our clients
|
||||||
ClientModel dummyClient = new ClientManager().createClient(appRealm, "dummy-client");
|
ClientModel dummyClient = new ClientManager().createClient(appRealm, "dummy-client");
|
||||||
dummyClient.setClientAuthenticatorType(PassThroughClientAuthenticator.PROVIDER_ID);
|
dummyClient.setClientAuthenticatorType(PassThroughClientAuthenticator.PROVIDER_ID);
|
||||||
appRealm.getClientByClientId("test-app").setClientAuthenticatorType(PassThroughClientAuthenticator.PROVIDER_ID);
|
dummyClient.setDirectAccessGrantsEnabled(true);
|
||||||
|
|
||||||
|
ClientModel testApp = appRealm.getClientByClientId("test-app");
|
||||||
|
testApp.setClientAuthenticatorType(PassThroughClientAuthenticator.PROVIDER_ID);
|
||||||
|
testApp.setDirectAccessGrantsEnabled(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -218,7 +222,7 @@ public class CustomFlowTest {
|
||||||
.client(clientId)
|
.client(clientId)
|
||||||
.user(userId)
|
.user(userId)
|
||||||
.session(accessToken.getSessionState())
|
.session(accessToken.getSessionState())
|
||||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||||
.detail(Details.USERNAME, login)
|
.detail(Details.USERNAME, login)
|
||||||
|
|
|
@ -48,6 +48,7 @@ public class JaxrsBasicAuthTest {
|
||||||
app.setEnabled(true);
|
app.setEnabled(true);
|
||||||
app.setSecret("password");
|
app.setSecret("password");
|
||||||
app.setFullScopeAllowed(true);
|
app.setFullScopeAllowed(true);
|
||||||
|
app.setDirectAccessGrantsEnabled(true);
|
||||||
|
|
||||||
JaxrsBasicAuthTest.appRealm = appRealm;
|
JaxrsBasicAuthTest.appRealm = appRealm;
|
||||||
}
|
}
|
||||||
|
|
|
@ -456,7 +456,7 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
|
|
||||||
public static void uploadSP(String AUTH_SERVER_URL) {
|
public static void uploadSP(String AUTH_SERVER_URL) {
|
||||||
try {
|
try {
|
||||||
Keycloak keycloak = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID, null);
|
Keycloak keycloak = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID, null);
|
||||||
RealmResource admin = keycloak.realm("demo");
|
RealmResource admin = keycloak.realm("demo");
|
||||||
|
|
||||||
admin.toRepresentation();
|
admin.toRepresentation();
|
||||||
|
|
|
@ -62,15 +62,19 @@ public class GroupTest {
|
||||||
@Override
|
@Override
|
||||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
ClientModel app = new ClientManager(manager).createClient(appRealm, "resource-owner");
|
ClientModel app = new ClientManager(manager).createClient(appRealm, "resource-owner");
|
||||||
|
app.setDirectAccessGrantsEnabled(true);
|
||||||
app.setSecret("secret");
|
app.setSecret("secret");
|
||||||
|
|
||||||
|
app = appRealm.getClientByClientId("test-app");
|
||||||
|
app.setDirectAccessGrantsEnabled(true);
|
||||||
|
|
||||||
UserModel user = session.users().addUser(appRealm, "direct-login");
|
UserModel user = session.users().addUser(appRealm, "direct-login");
|
||||||
user.setEmail("direct-login@localhost");
|
user.setEmail("direct-login@localhost");
|
||||||
user.setEnabled(true);
|
user.setEnabled(true);
|
||||||
|
|
||||||
|
|
||||||
session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
|
session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
|
||||||
keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID);
|
keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -257,7 +261,7 @@ public class GroupTest {
|
||||||
.client(clientId)
|
.client(clientId)
|
||||||
.user(userId)
|
.user(userId)
|
||||||
.session(accessToken.getSessionState())
|
.session(accessToken.getSessionState())
|
||||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||||
.detail(Details.USERNAME, login)
|
.detail(Details.USERNAME, login)
|
||||||
|
|
|
@ -90,7 +90,7 @@ public class ImportTest extends AbstractModelTest {
|
||||||
Assert.assertEquals(0, session.users().getFederatedIdentities(user, realm).size());
|
Assert.assertEquals(0, session.users().getFederatedIdentities(user, realm).size());
|
||||||
|
|
||||||
List<ClientModel> resources = realm.getClients();
|
List<ClientModel> resources = realm.getClients();
|
||||||
Assert.assertEquals(7, resources.size());
|
Assert.assertEquals(8, resources.size());
|
||||||
|
|
||||||
// Test applications imported
|
// Test applications imported
|
||||||
ClientModel application = realm.getClientByClientId("Application");
|
ClientModel application = realm.getClientByClientId("Application");
|
||||||
|
@ -101,7 +101,7 @@ public class ImportTest extends AbstractModelTest {
|
||||||
Assert.assertNotNull(otherApp);
|
Assert.assertNotNull(otherApp);
|
||||||
Assert.assertNull(nonExisting);
|
Assert.assertNull(nonExisting);
|
||||||
Map<String, ClientModel> clients = realm.getClientNameMap();
|
Map<String, ClientModel> clients = realm.getClientNameMap();
|
||||||
Assert.assertEquals(7, clients.size());
|
Assert.assertEquals(8, clients.size());
|
||||||
Assert.assertTrue(clients.values().contains(application));
|
Assert.assertTrue(clients.values().contains(application));
|
||||||
Assert.assertTrue(clients.values().contains(otherApp));
|
Assert.assertTrue(clients.values().contains(otherApp));
|
||||||
Assert.assertTrue(clients.values().contains(accountApp));
|
Assert.assertTrue(clients.values().contains(accountApp));
|
||||||
|
|
|
@ -92,7 +92,14 @@ import static org.junit.Assert.*;
|
||||||
public class AccessTokenTest {
|
public class AccessTokenTest {
|
||||||
|
|
||||||
@ClassRule
|
@ClassRule
|
||||||
public static KeycloakRule keycloakRule = new KeycloakRule();
|
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public WebRule webRule = new WebRule(this);
|
public WebRule webRule = new WebRule(this);
|
||||||
|
|
|
@ -61,6 +61,7 @@ public class ClientAuthSignedJWTTest {
|
||||||
app1.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
app1.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
||||||
|
|
||||||
ClientModel app2 = appRealm.addClient("client2");
|
ClientModel app2 = appRealm.addClient("client2");
|
||||||
|
app2.setDirectAccessGrantsEnabled(true);
|
||||||
new ClientManager(manager).enableServiceAccount(app2);
|
new ClientManager(manager).enableServiceAccount(app2);
|
||||||
app2.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
app2.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
||||||
|
|
||||||
|
@ -189,7 +190,7 @@ public class ClientAuthSignedJWTTest {
|
||||||
events.expectLogin()
|
events.expectLogin()
|
||||||
.client("client2")
|
.client("client2")
|
||||||
.session(accessToken.getSessionState())
|
.session(accessToken.getSessionState())
|
||||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||||
.detail(Details.USERNAME, "test-user@localhost")
|
.detail(Details.USERNAME, "test-user@localhost")
|
||||||
|
|
|
@ -67,6 +67,7 @@ public class OfflineTokenTest {
|
||||||
appRealm.setSsoSessionIdleTimeout(30);
|
appRealm.setSsoSessionIdleTimeout(30);
|
||||||
|
|
||||||
ClientModel app = new ClientManager(manager).createClient(appRealm, "offline-client");
|
ClientModel app = new ClientManager(manager).createClient(appRealm, "offline-client");
|
||||||
|
app.setDirectAccessGrantsEnabled(true);
|
||||||
app.setSecret("secret1");
|
app.setSecret("secret1");
|
||||||
String testAppRedirectUri = appRealm.getClientByClientId("test-app").getRedirectUris().iterator().next();
|
String testAppRedirectUri = appRealm.getClientByClientId("test-app").getRedirectUris().iterator().next();
|
||||||
offlineClientAppUri = UriUtils.getOrigin(testAppRedirectUri) + "/offline-client";
|
offlineClientAppUri = UriUtils.getOrigin(testAppRedirectUri) + "/offline-client";
|
||||||
|
@ -319,7 +320,7 @@ public class OfflineTokenTest {
|
||||||
.client("offline-client")
|
.client("offline-client")
|
||||||
.user(userId)
|
.user(userId)
|
||||||
.session(token.getSessionState())
|
.session(token.getSessionState())
|
||||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||||
.detail(Details.TOKEN_ID, token.getId())
|
.detail(Details.TOKEN_ID, token.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
|
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
||||||
|
@ -361,7 +362,7 @@ public class OfflineTokenTest {
|
||||||
.client("offline-client")
|
.client("offline-client")
|
||||||
.user(userId)
|
.user(userId)
|
||||||
.session(token.getSessionState())
|
.session(token.getSessionState())
|
||||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||||
.detail(Details.TOKEN_ID, token.getId())
|
.detail(Details.TOKEN_ID, token.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
|
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
||||||
|
|
|
@ -71,7 +71,14 @@ import static org.junit.Assert.assertNull;
|
||||||
public class RefreshTokenTest {
|
public class RefreshTokenTest {
|
||||||
|
|
||||||
@ClassRule
|
@ClassRule
|
||||||
public static KeycloakRule keycloakRule = new KeycloakRule();
|
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public WebRule webRule = new WebRule(this);
|
public WebRule webRule = new WebRule(this);
|
||||||
|
|
|
@ -35,9 +35,11 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
||||||
@Override
|
@Override
|
||||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
ClientModel app = new ClientManager(manager).createClient(appRealm, "resource-owner");
|
ClientModel app = new ClientManager(manager).createClient(appRealm, "resource-owner");
|
||||||
|
app.setDirectAccessGrantsEnabled(true);
|
||||||
app.setSecret("secret");
|
app.setSecret("secret");
|
||||||
|
|
||||||
ClientModel app2 = new ClientManager(manager).createClient(appRealm, "resource-owner-public");
|
ClientModel app2 = new ClientManager(manager).createClient(appRealm, "resource-owner-public");
|
||||||
|
app2.setDirectAccessGrantsEnabled(true);
|
||||||
app2.setPublicClient(true);
|
app2.setPublicClient(true);
|
||||||
|
|
||||||
UserModel user = session.users().addUser(appRealm, "direct-login");
|
UserModel user = session.users().addUser(appRealm, "direct-login");
|
||||||
|
@ -94,7 +96,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
||||||
.client(clientId)
|
.client(clientId)
|
||||||
.user(userId)
|
.user(userId)
|
||||||
.session(accessToken.getSessionState())
|
.session(accessToken.getSessionState())
|
||||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||||
.detail(Details.USERNAME, login)
|
.detail(Details.USERNAME, login)
|
||||||
|
@ -130,7 +132,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
||||||
events.expectLogin()
|
events.expectLogin()
|
||||||
.client("resource-owner")
|
.client("resource-owner")
|
||||||
.session(accessToken.getSessionState())
|
.session(accessToken.getSessionState())
|
||||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||||
.removeDetail(Details.CODE_ID)
|
.removeDetail(Details.CODE_ID)
|
||||||
|
@ -191,6 +193,41 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void grantAccessTokenClientNotAllowed() throws Exception {
|
||||||
|
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
ClientModel client = appRealm.getClientByClientId("resource-owner");
|
||||||
|
client.setDirectAccessGrantsEnabled(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
oauth.clientId("resource-owner");
|
||||||
|
|
||||||
|
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "test-user@localhost", "password");
|
||||||
|
|
||||||
|
assertEquals(400, response.getStatusCode());
|
||||||
|
|
||||||
|
assertEquals("invalid_grant", response.getError());
|
||||||
|
|
||||||
|
events.expectLogin()
|
||||||
|
.client("resource-owner")
|
||||||
|
.session((String) null)
|
||||||
|
.clearDetails()
|
||||||
|
.error(Errors.NOT_ALLOWED)
|
||||||
|
.user((String) null)
|
||||||
|
.assertEvent();
|
||||||
|
|
||||||
|
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
ClientModel client = appRealm.getClientByClientId("resource-owner");
|
||||||
|
client.setDirectAccessGrantsEnabled(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void grantAccessTokenVerifyEmail() throws Exception {
|
public void grantAccessTokenVerifyEmail() throws Exception {
|
||||||
|
|
||||||
|
@ -286,7 +323,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
||||||
events.expectLogin()
|
events.expectLogin()
|
||||||
.client("resource-owner")
|
.client("resource-owner")
|
||||||
.session((String) null)
|
.session((String) null)
|
||||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||||
.removeDetail(Details.CODE_ID)
|
.removeDetail(Details.CODE_ID)
|
||||||
.removeDetail(Details.REDIRECT_URI)
|
.removeDetail(Details.REDIRECT_URI)
|
||||||
.removeDetail(Details.CONSENT)
|
.removeDetail(Details.CONSENT)
|
||||||
|
@ -308,7 +345,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
||||||
.client("resource-owner")
|
.client("resource-owner")
|
||||||
.user((String) null)
|
.user((String) null)
|
||||||
.session((String) null)
|
.session((String) null)
|
||||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||||
.detail(Details.USERNAME, "invalid")
|
.detail(Details.USERNAME, "invalid")
|
||||||
.removeDetail(Details.CODE_ID)
|
.removeDetail(Details.CODE_ID)
|
||||||
.removeDetail(Details.REDIRECT_URI)
|
.removeDetail(Details.REDIRECT_URI)
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.UserInfo;
|
import org.keycloak.representations.UserInfo;
|
||||||
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.WebResource;
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
import org.keycloak.testsuite.rule.WebRule;
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
|
@ -56,7 +57,14 @@ import static org.junit.Assert.assertNotNull;
|
||||||
public class UserInfoTest {
|
public class UserInfoTest {
|
||||||
|
|
||||||
@ClassRule
|
@ClassRule
|
||||||
public static KeycloakRule keycloakRule = new KeycloakRule();
|
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public WebRule webRule = new WebRule(this);
|
public WebRule webRule = new WebRule(this);
|
||||||
|
|
|
@ -457,7 +457,7 @@ public class SamlBindingTest {
|
||||||
|
|
||||||
public static void uploadSP() {
|
public static void uploadSP() {
|
||||||
try {
|
try {
|
||||||
Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID, null);
|
Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID, null);
|
||||||
RealmResource admin = keycloak.realm("demo");
|
RealmResource admin = keycloak.realm("demo");
|
||||||
|
|
||||||
admin.toRepresentation();
|
admin.toRepresentation();
|
||||||
|
|
|
@ -120,6 +120,7 @@
|
||||||
{
|
{
|
||||||
"name": "customer-portal",
|
"name": "customer-portal",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"directAccessGrantsEnabled": true,
|
||||||
"adminUrl": "http://localhost:8081/customer-portal",
|
"adminUrl": "http://localhost:8081/customer-portal",
|
||||||
"baseUrl": "http://localhost:8081/customer-portal",
|
"baseUrl": "http://localhost:8081/customer-portal",
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
|
|
|
@ -147,7 +147,7 @@
|
||||||
"clientId": "clientId",
|
"clientId": "clientId",
|
||||||
"clientSecret": "clientSecret",
|
"clientSecret": "clientSecret",
|
||||||
"prompt": "prompt",
|
"prompt": "prompt",
|
||||||
"authorizationUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/tokens/login",
|
"authorizationUrl": "http://localhost:8081/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/auth",
|
||||||
"tokenUrl": "http://localhost:8081/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/token",
|
"tokenUrl": "http://localhost:8081/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/token",
|
||||||
"userInfoUrl": "http://localhost:8081/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/userinfo",
|
"userInfoUrl": "http://localhost:8081/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/userinfo",
|
||||||
"defaultScope": "email profile"
|
"defaultScope": "email profile"
|
||||||
|
@ -163,10 +163,10 @@
|
||||||
"clientId": "broker-app",
|
"clientId": "broker-app",
|
||||||
"clientSecret": "secret",
|
"clientSecret": "secret",
|
||||||
"prompt": "login",
|
"prompt": "login",
|
||||||
"authorizationUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/tokens/login",
|
"authorizationUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/auth",
|
||||||
"tokenUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/token",
|
"tokenUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/token",
|
||||||
"userInfoUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/userinfo",
|
"userInfoUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/userinfo",
|
||||||
"logoutUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/tokens/logout",
|
"logoutUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/logout",
|
||||||
"defaultScope": "email profile",
|
"defaultScope": "email profile",
|
||||||
"backchannelSupported": "true"
|
"backchannelSupported": "true"
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,6 +156,7 @@
|
||||||
"name": "Applicationn",
|
"name": "Applicationn",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"implicitFlowEnabled": true,
|
"implicitFlowEnabled": true,
|
||||||
|
"directAccessGrantsEnabled": true,
|
||||||
"nodeReRegistrationTimeout": 50,
|
"nodeReRegistrationTimeout": 50,
|
||||||
"registeredNodes": {
|
"registeredNodes": {
|
||||||
"node1": 10,
|
"node1": 10,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue