[KEYCLOAK-10868] - Deploy JavaScript code directly to Keycloak server

Conflicts:
	testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java

(cherry picked from commit 338fe2ae47a1494e786030eb39f908c964ea76c4)
This commit is contained in:
Pedro Igor 2019-08-21 11:41:02 -03:00 committed by Stian Thorgersen
parent bad9e29c15
commit bb4ff55229
48 changed files with 1387 additions and 74 deletions

View file

@ -0,0 +1,84 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.authorization.policy.provider.js;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.ScriptModel;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.provider.ScriptProviderMetadata;
import org.keycloak.scripting.ScriptingProvider;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public final class DeployedScriptPolicyFactory extends JSPolicyProviderFactory {
private final ScriptProviderMetadata metadata;
public DeployedScriptPolicyFactory(ScriptProviderMetadata metadata) {
this.metadata = metadata;
}
@Override
public String getId() {
return metadata.getId();
}
@Override
public String getName() {
return metadata.getName();
}
@Override
protected boolean isDeployed() {
return true;
}
@Override
public boolean isInternal() {
return false;
}
@Override
public JSPolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
JSPolicyRepresentation representation = new JSPolicyRepresentation();
representation.setId(policy.getId());
representation.setName(policy.getName());
representation.setDescription(metadata.getDescription());
representation.setType(getId());
representation.setCode(metadata.getCode());
return representation;
}
@Override
protected ScriptModel getScriptModel(Policy policy, RealmModel realm, ScriptingProvider scripting) {
return scripting.createScript(realm.getId(), ScriptModel.TEXT_JAVASCRIPT, metadata.getName(), metadata.getCode(),
metadata.getDescription());
}
@Override
public void onCreate(Policy policy, JSPolicyRepresentation representation, AuthorizationProvider authorization) {
representation.setDescription(metadata.getDescription());
policy.setDescription(metadata.getDescription());
super.onCreate(policy, representation, authorization);
}
}

View file

@ -5,6 +5,7 @@ import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
@ -56,17 +57,17 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory<JSPolicyRe
@Override
public void onCreate(Policy policy, JSPolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation.getCode());
updatePolicy(policy, representation.getCode(), authorization);
}
@Override
public void onUpdate(Policy policy, JSPolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation.getCode());
updatePolicy(policy, representation.getCode(), authorization);
}
@Override
public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation.getConfig().get("code"));
updatePolicy(policy, representation.getConfig().get("code"), authorization);
}
@Override
@ -96,6 +97,11 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory<JSPolicyRe
return "js";
}
@Override
public boolean isInternal() {
return !Profile.isFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS);
}
private EvaluatableScriptAdapter getEvaluatableScript(final AuthorizationProvider authz, final Policy policy) {
return scriptCache.computeIfAbsent(policy.getId(), id -> {
final ScriptingProvider scripting = authz.getKeycloakSession().getProvider(ScriptingProvider.class);
@ -104,7 +110,7 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory<JSPolicyRe
});
}
private ScriptModel getScriptModel(final Policy policy, final RealmModel realm, final ScriptingProvider scripting) {
protected ScriptModel getScriptModel(final Policy policy, final RealmModel realm, final ScriptingProvider scripting) {
String scriptName = policy.getName();
String scriptCode = policy.getConfig().get("code");
String scriptDescription = policy.getDescription();
@ -113,8 +119,15 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory<JSPolicyRe
return scripting.createScript(realm.getId(), ScriptModel.TEXT_JAVASCRIPT, scriptName, scriptCode, scriptDescription);
}
private void updatePolicy(Policy policy, String code) {
private void updatePolicy(Policy policy, String code, AuthorizationProvider authorization) {
scriptCache.remove(policy.getId());
if (!Profile.isFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS) && !authorization.getKeycloakSession().getAttributeOrDefault("ALLOW_CREATE_POLICY", false) && !isDeployed()) {
throw new RuntimeException("Script upload is disabled");
}
policy.putConfig("code", code);
}
protected boolean isDeployed() {
return false;
}
}

View file

@ -48,6 +48,11 @@ public abstract class AbstractPermissionProvider implements PolicyProvider {
if (effect == null) {
PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());
if (policyProvider == null) {
throw new RuntimeException("No policy provider found for policy [" + associatedPolicy.getType() + "]");
}
policyProvider.evaluate(defaultEvaluation);
evaluation.denyIfNoEffect();
decisions.put(permission, defaultEvaluation.getEffect());

View file

@ -110,6 +110,11 @@ public class Profile {
disabledFeatures.add(f);
} else if (DEPRECATED.equals(f.getType())) {
logger.warnf("Deprecated feature enabled: " + f.name().toLowerCase());
if (Feature.UPLOAD_SCRIPTS.equals(f)) {
previewFeatures.add(Feature.SCRIPTS);
disabledFeatures.remove(Feature.SCRIPTS);
logger.warnf("Preview feature enabled: " + Feature.SCRIPTS.name().toLowerCase());
}
}
break;
case PREVIEW:

View file

@ -25,8 +25,11 @@ public class JSPolicyRepresentation extends AbstractPolicyRepresentation {
@Override
public String getType() {
if (super.getType() == null) {
return "js";
}
return super.getType();
}
public String getCode() {
return code;

View file

@ -0,0 +1,79 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.representations.provider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
public class ScriptProviderDescriptor {
public static final String AUTHENTICATORS = "authenticators";
public static final String POLICIES = "policies";
public static final String MAPPERS = "mappers";
private Map<String, List<ScriptProviderMetadata>> providers = new HashMap<>();
@JsonUnwrapped
@JsonGetter
public Map<String, List<ScriptProviderMetadata>> getProviders() {
return providers;
}
@JsonSetter
public void setAuthenticators(List<ScriptProviderMetadata> metadata) {
providers.put(AUTHENTICATORS, metadata);
}
@JsonSetter
public void setPolicies(List<ScriptProviderMetadata> metadata) {
providers.put(POLICIES, metadata);
}
@JsonSetter
public void setMappers(List<ScriptProviderMetadata> metadata) {
providers.put(MAPPERS, metadata);
}
public void addAuthenticator(String name, String fileName) {
addProvider(AUTHENTICATORS, name, fileName, null);
}
private void addProvider(String type, String name, String fileName, String description) {
List<ScriptProviderMetadata> authenticators = providers.get(type);
if (authenticators == null) {
authenticators = new ArrayList<>();
providers.put(type, authenticators);
}
authenticators.add(new ScriptProviderMetadata(name, fileName, description));
}
public void addPolicy(String name, String fileName) {
addProvider(POLICIES, name, fileName, null);
}
public void addMapper(String name, String fileName) {
addProvider(MAPPERS, name, fileName, null);
}
}

View file

@ -0,0 +1,73 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.representations.provider;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class ScriptProviderMetadata {
@JsonIgnore
private String id;
private String name;
private String fileName;
private String description;
@JsonIgnore
private String code;
public ScriptProviderMetadata() {
}
public ScriptProviderMetadata(String name, String fileName, String description) {
this.name = name;
this.fileName = fileName;
this.description = description;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFileName() {
return fileName;
}
public String getDescription() {
return description;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}

View file

@ -45,6 +45,10 @@
<module name="org.keycloak.keycloak-services"/>
<module name="org.keycloak.keycloak-server-spi-private"/>
<module name="org.keycloak.keycloak-wildfly-adapter" optional="true"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-server-spi"/>
<module name="org.keycloak.keycloak-authz-policy-common"/>
<module name="org.jboss.metadata"/>
</dependencies>
</module>

View file

@ -78,14 +78,12 @@ public final class AuthorizationProvider implements Provider {
private final PolicyEvaluator policyEvaluator;
private StoreFactory storeFactory;
private StoreFactory storeFactoryDelegate;
private final Map<String, PolicyProviderFactory> policyProviderFactories;
private final KeycloakSession keycloakSession;
private final RealmModel realm;
public AuthorizationProvider(KeycloakSession session, RealmModel realm, Map<String, PolicyProviderFactory> policyProviderFactories, PolicyEvaluator policyEvaluator) {
public AuthorizationProvider(KeycloakSession session, RealmModel realm, PolicyEvaluator policyEvaluator) {
this.keycloakSession = session;
this.realm = realm;
this.policyProviderFactories = policyProviderFactories;
this.policyEvaluator = policyEvaluator;
}
@ -131,7 +129,8 @@ public final class AuthorizationProvider implements Provider {
* @return a {@link List} containing all registered {@link PolicyProviderFactory}
*/
public Collection<PolicyProviderFactory> getProviderFactories() {
return this.policyProviderFactories.values();
return keycloakSession.getKeycloakSessionFactory().getProviderFactories(PolicyProvider.class).stream().map(
PolicyProviderFactory.class::cast).collect(Collectors.toList());
}
/**
@ -141,8 +140,8 @@ public final class AuthorizationProvider implements Provider {
* @param <F> the expected type of the provider
* @return a {@link PolicyProviderFactory} with the given <code>type</code>
*/
public <F extends PolicyProviderFactory> F getProviderFactory(String type) {
return (F) policyProviderFactories.get(type);
public PolicyProviderFactory getProviderFactory(String type) {
return (PolicyProviderFactory) keycloakSession.getKeycloakSessionFactory().getProviderFactory(PolicyProvider.class, type);
}
/**
@ -153,7 +152,7 @@ public final class AuthorizationProvider implements Provider {
* @return a {@link PolicyProvider} with the given <code>type</code>
*/
public <P extends PolicyProvider> P getProvider(String type) {
PolicyProviderFactory policyProviderFactory = policyProviderFactories.get(type);
PolicyProviderFactory policyProviderFactory = getProviderFactory(type);
if (policyProviderFactory == null) {
return null;

View file

@ -1,14 +1,20 @@
package org.keycloak.provider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class KeycloakDeploymentInfo {
private String name;
private boolean services;
private boolean themes;
private boolean themeResources;
private Map<Class<? extends Spi>, List<ProviderFactory>> providers = new HashMap<>();
public boolean isProvider() {
return services || themes || themeResources;
return services || themes || themeResources || !providers.isEmpty();
}
public boolean hasServices() {
@ -53,4 +59,12 @@ public class KeycloakDeploymentInfo {
themeResources = true;
return this;
}
public void addProvider(Class<? extends Spi> spi, ProviderFactory factory) {
providers.computeIfAbsent(spi, key -> new ArrayList<>()).add(factory);
}
public Map<Class<? extends Spi>, List<ProviderFactory>> getProviders() {
return providers;
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.authentication.authenticators.browser;
import java.util.HashMap;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.common.Profile;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.provider.ScriptProviderMetadata;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public final class DeployedScriptAuthenticatorFactory extends ScriptBasedAuthenticatorFactory {
private final AuthenticatorConfigModel model;
public DeployedScriptAuthenticatorFactory(ScriptProviderMetadata metadata) {
model = new AuthenticatorConfigModel();
model.setId(metadata.getId());
model.setAlias(metadata.getName());
model.setConfig(new HashMap<>());
model.getConfig().put("scriptName", metadata.getName());
model.getConfig().put("scriptCode", metadata.getCode());
model.getConfig().put("scriptDescription", metadata.getDescription());
}
@Override
public Authenticator create(KeycloakSession session) {
return new ScriptBasedAuthenticator() {
@Override
protected AuthenticatorConfigModel getAuthenticatorConfig(AuthenticationFlowContext context) {
return model;
}
};
}
@Override
public String getId() {
return model.getId();
}
@Override
public boolean isConfigurable() {
return false;
}
@Override
public boolean isUserSetupAllowed() {
return false;
}
@Override
public String getDisplayType() {
return model.getAlias();
}
@Override
public String getHelpText() {
return model.getAlias();
}
@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS);
}
}

View file

@ -20,6 +20,7 @@ import org.jboss.logging.Logger;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.ScriptModel;
@ -132,15 +133,21 @@ public class ScriptBasedAuthenticator implements Authenticator {
}
private boolean hasAuthenticatorConfig(AuthenticationFlowContext context) {
return context != null
&& context.getAuthenticatorConfig() != null
&& context.getAuthenticatorConfig().getConfig() != null
&& !context.getAuthenticatorConfig().getConfig().isEmpty();
if (context == null)
return false;
AuthenticatorConfigModel config = getAuthenticatorConfig(context);
return config != null
&& config.getConfig() != null
&& !config.getConfig().isEmpty();
}
protected AuthenticatorConfigModel getAuthenticatorConfig(AuthenticationFlowContext context) {
return context.getAuthenticatorConfig();
}
private InvocableScriptAdapter getInvocableScriptAdapter(AuthenticationFlowContext context) {
Map<String, String> config = context.getAuthenticatorConfig().getConfig();
Map<String, String> config = getAuthenticatorConfig(context).getConfig();
String scriptName = config.get(SCRIPT_NAME);
String scriptCode = config.get(SCRIPT_CODE);

View file

@ -153,6 +153,6 @@ public class ScriptBasedAuthenticatorFactory implements AuthenticatorFactory, En
@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS);
return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS) && Profile.isFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS);
}
}

View file

@ -18,26 +18,18 @@
package org.keycloak.authorization;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.keycloak.Config;
import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
import org.keycloak.authorization.policy.evaluation.PolicyEvaluator;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class DefaultAuthorizationProviderFactory implements AuthorizationProviderFactory {
private Map<String, PolicyProviderFactory> policyProviderFactories;
private PolicyEvaluator policyEvaluator = new DefaultPolicyEvaluator();
@Override
@ -51,7 +43,6 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide
@Override
public void postInit(KeycloakSessionFactory factory) {
policyProviderFactories = configurePolicyProviderFactories(factory);
}
@Override
@ -66,21 +57,6 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide
@Override
public AuthorizationProvider create(KeycloakSession session, RealmModel realm) {
return new AuthorizationProvider(session, realm, policyProviderFactories, policyEvaluator);
return new AuthorizationProvider(session, realm, policyEvaluator);
}
private Map<String, PolicyProviderFactory> configurePolicyProviderFactories(KeycloakSessionFactory keycloakSessionFactory) {
List<ProviderFactory> providerFactories = keycloakSessionFactory.getProviderFactories(PolicyProvider.class);
if (providerFactories.isEmpty()) {
throw new RuntimeException("Could not find any policy provider.");
}
HashMap<String, PolicyProviderFactory> providers = new HashMap<>();
providerFactories.forEach(providerFactory -> providers.put(providerFactory.getId(), (PolicyProviderFactory) providerFactory));
return providers;
}
}

View file

@ -109,7 +109,9 @@ public class PolicyResourceService {
PolicyStore policyStore = storeFactory.getPolicyStore();
PolicyProviderFactory resource = getProviderFactory(policy.getType());
if (resource != null) {
resource.onRemove(policy, authorization);
}
policyStore.delete(policy.getId());

View file

@ -206,6 +206,8 @@ public class ResourceServerService {
defaultPolicy.setConfig(defaultPolicyConfig);
session.setAttribute("ALLOW_CREATE_POLICY", true);
getPolicyResource().create(defaultPolicy);
return defaultPolicy;

View file

@ -0,0 +1,80 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.oidc.mappers;
import java.util.List;
import org.keycloak.common.Profile;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.representations.provider.ScriptProviderMetadata;
public final class DeployedScriptOIDCProtocolMapper extends ScriptBasedOIDCProtocolMapper {
private static final List<ProviderConfigProperty> configProperties;
static {
configProperties = ProviderConfigurationBuilder.create()
.property()
.name(ProtocolMapperUtils.MULTIVALUED)
.label(ProtocolMapperUtils.MULTIVALUED_LABEL)
.helpText(ProtocolMapperUtils.MULTIVALUED_HELP_TEXT)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue(false)
.add()
.build();
OIDCAttributeMapperHelper.addAttributeConfig(configProperties, UserPropertyMapper.class);
}
private final ScriptProviderMetadata metadata;
public DeployedScriptOIDCProtocolMapper(ScriptProviderMetadata metadata) {
this.metadata = metadata;
}
@Override
public String getId() {
return metadata.getId();
}
@Override
public String getDisplayType() {
return metadata.getName();
}
@Override
public String getHelpText() {
return metadata.getDescription();
}
@Override
protected String getScriptCode(ProtocolMapperModel mapperModel) {
return metadata.getCode();
}
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS);
}
}

View file

@ -29,6 +29,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperConfigException;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.representations.IDToken;
@ -43,7 +44,8 @@ import java.util.List;
*
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
*/
public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper,
EnvironmentDependentProviderFactory {
public static final String PROVIDER_ID = "oidc-script-based-protocol-mapper";
@ -115,8 +117,9 @@ public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper im
return "Evaluates a JavaScript function to produce a token claim based on context information.";
}
@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS);
return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS) && Profile.isFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS);
}
@Override
@ -128,7 +131,7 @@ public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper im
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) {
UserModel user = userSession.getUser();
String scriptSource = mappingModel.getConfig().get(SCRIPT);
String scriptSource = getScriptCode(mappingModel);
RealmModel realm = userSession.getRealm();
ScriptingProvider scripting = keycloakSession.getProvider(ScriptingProvider.class);
@ -156,7 +159,7 @@ public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper im
@Override
public void validateConfig(KeycloakSession session, RealmModel realm, ProtocolMapperContainerModel client, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException {
String scriptCode = mapperModel.getConfig().get(SCRIPT);
String scriptCode = getScriptCode(mapperModel);
if (scriptCode == null) {
return;
}
@ -171,6 +174,10 @@ public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper im
}
}
protected String getScriptCode(ProtocolMapperModel mapperModel) {
return mapperModel.getConfig().get(SCRIPT);
}
public static ProtocolMapperModel create(String name,
String userAttribute,
String tokenClaimName, String claimType,

View file

@ -0,0 +1,43 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.provider;
import java.util.Collections;
import java.util.List;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
final class DeploymentProviderLoader implements ProviderLoader {
private final KeycloakDeploymentInfo info;
DeploymentProviderLoader(KeycloakDeploymentInfo info) {
this.info = info;
}
@Override
public List<Spi> loadSpis() {
return Collections.emptyList();
}
@Override
public List<ProviderFactory> load(Spi spi) {
return info.getProviders().getOrDefault(spi.getClass(), Collections.emptyList());
}
}

View file

@ -49,6 +49,7 @@ public class ProviderManager {
logger.debugv("Provider loaders {0}", factories);
loaders.add(new DefaultProviderLoader(info, baseClassLoader));
loaders.add(new DeploymentProviderLoader(info));
if (resources != null) {
for (String r : resources) {

View file

@ -77,6 +77,7 @@ public class ProfileAssume {
}
private static boolean isFeatureEnabled(Profile.Feature feature) {
updateProfile();
return !disabledFeatures.contains(feature.name());
}
}

View file

@ -32,6 +32,7 @@ import org.keycloak.admin.client.resource.AuthenticationManagementResource;
import org.keycloak.admin.client.resource.RealmsResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.common.Profile;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.Time;
import org.keycloak.representations.idm.ClientRepresentation;
@ -61,6 +62,7 @@ import org.keycloak.testsuite.util.TestEventsLogger;
import org.openqa.selenium.WebDriver;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
@ -81,6 +83,7 @@ import java.util.stream.Collectors;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.admin.Users.setPasswordFor;
import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN;
@ -149,6 +152,7 @@ public abstract class AbstractKeycloakTest {
private PropertiesConfiguration constantsProperties;
private boolean resetTimeOffset;
private List<Profile.Feature> enabledFeatures = new ArrayList<>();
@Before
public void beforeAbstractKeycloakTest() throws Exception {
@ -226,6 +230,10 @@ public abstract class AbstractKeycloakTest {
testContext.getCleanups().clear();
}
for (Profile.Feature feature : enabledFeatures) {
disableFeature(feature);
}
postAfterAbstractKeycloak();
// Remove all browsers from queue
@ -631,4 +639,17 @@ public abstract class AbstractKeycloakTest {
}
return in;
}
protected void enableFeature(Profile.Feature feature) {
enabledFeatures.add(feature);
try (Response response = getTestingClient().testing().enableFeature(feature.toString())) {
assertEquals(200, response.getStatus());
}
}
protected void disableFeature(Profile.Feature feature) {
try (Response response = getTestingClient().testing().disableFeature(feature.toString())) {
assertEquals(200, response.getStatus());
}
}
}

View file

@ -21,6 +21,7 @@ import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.utils.io.IOUtil.loadJson;
@ -164,6 +165,7 @@ public abstract class AbstractBasePhotozExampleAdapterTest extends AbstractPhoto
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
enableFeature(UPLOAD_SCRIPTS);
RealmRepresentation realm = loadRealm(new File(TEST_APPS_HOME_DIR + "/photoz/photoz-realm.json"));
realm.setAccessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY); // seconds

View file

@ -18,7 +18,7 @@ package org.keycloak.testsuite.adapter.example.authorization;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.junit.BeforeClass;
import org.junit.Before;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
@ -27,7 +27,6 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.util.UIUtils;
import org.openqa.selenium.By;
@ -42,6 +41,7 @@ import java.net.URL;
import java.util.List;
import static org.junit.Assert.assertFalse;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.utils.io.IOUtil.loadJson;
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm;
@ -58,6 +58,11 @@ public abstract class AbstractBaseServletAuthzAdapterTest extends AbstractExampl
@ArquillianResource
private Deployer deployer;
@Before
public void onBefore() {
enableFeature(UPLOAD_SCRIPTS);
}
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
testRealms.add(

View file

@ -18,6 +18,7 @@ package org.keycloak.testsuite.adapter.example.authorization;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm;
@ -32,6 +33,7 @@ import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
@ -41,6 +43,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
@ -69,6 +72,7 @@ public class ServletPolicyEnforcerTest extends AbstractExampleAdapterTest {
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
enableFeature(UPLOAD_SCRIPTS);
testRealms.add(
loadRealm(new File(TEST_APPS_HOME_DIR + "/servlet-policy-enforcer/servlet-policy-enforcer-authz-realm.json")));
}

View file

@ -28,6 +28,7 @@ import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.common.Profile;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.adapter.page.AngularCorsProductTestApp;
@ -108,6 +109,7 @@ public class CorsExampleAdapterTest extends AbstractExampleAdapterTest {
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
enableFeature(Profile.Feature.UPLOAD_SCRIPTS);
testRealms.add(
loadRealm(new File(TEST_APPS_HOME_DIR + "/cors/cors-realm.json")));
}

View file

@ -50,6 +50,7 @@ import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentati
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
@ -957,13 +958,10 @@ public class PermissionsTest extends AbstractKeycloakTest {
invoke(new InvocationWithResponse() {
public void invoke(RealmResource realm, AtomicReference<Response> response) {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
PolicyRepresentation representation = new PolicyRepresentation();
ResourcePermissionRepresentation representation = new ResourcePermissionRepresentation();
representation.setName("Test PermissionsTest");
representation.setType("js");
HashMap<String, String> config = new HashMap<>();
config.put("code", "");
representation.setConfig(config);
response.set(authorization.policies().create(representation));
representation.addResource("Default Resource");
response.set(authorization.permissions().resource().create(representation));
}
}, AUTHORIZATION, true);
invoke(new Invocation() {

View file

@ -18,6 +18,7 @@
package org.keycloak.testsuite.admin.client.authorization;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
@ -36,6 +37,7 @@ import org.keycloak.testsuite.util.UserBuilder;
import javax.ws.rs.core.Response;
import static org.junit.Assert.assertEquals;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import java.util.List;
@ -46,6 +48,11 @@ public abstract class AbstractAuthorizationTest extends AbstractClientTest {
protected static final String RESOURCE_SERVER_CLIENT_ID = "resource-server-test";
@Before
public void onBefore() {
enableFeature(UPLOAD_SCRIPTS);
}
@Override
public void setDefaultPageUriParameters() {
super.setDefaultPageUriParameters();

View file

@ -24,10 +24,12 @@ import java.util.Collections;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.JSPoliciesResource;
import org.keycloak.admin.client.resource.JSPolicyResource;
import org.keycloak.common.Profile;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.idm.authorization.Logic;
@ -37,6 +39,11 @@ import org.keycloak.representations.idm.authorization.Logic;
*/
public class JSPolicyManagementTest extends AbstractPolicyManagementTest {
@Before
public void onBefore() {
enableFeature(Profile.Feature.UPLOAD_SCRIPTS);
}
@Test
public void testCreate() {
AuthorizationResource authorization = getClient().authorization();

View file

@ -35,6 +35,7 @@ import java.util.stream.Collectors;
import javax.security.cert.X509Certificate;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext;
@ -51,6 +52,7 @@ import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.common.Profile;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
@ -108,6 +110,11 @@ public class PolicyEnforcerClaimsTest extends AbstractKeycloakTest {
.build());
}
@Before
public void onBefore() {
enableFeature(Profile.Feature.UPLOAD_SCRIPTS);
}
@Test
public void testEnforceUMAAccessWithClaimsUsingBearerToken() {
initAuthorizationSettings(getClientResource("resource-server-uma-test"));

View file

@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import javax.security.cert.X509Certificate;
import javax.ws.rs.HttpMethod;
@ -126,6 +127,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
@Before
public void onBefore() {
enableFeature(UPLOAD_SCRIPTS);
initAuthorizationSettings(getClientResource(RESOURCE_SERVER_CLIENT_ID));
}

View file

@ -1,17 +1,23 @@
package org.keycloak.testsuite.authz;
import org.junit.BeforeClass;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import org.junit.Before;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.ProfileAssume;
/**
* @author mhajas
*/
public abstract class AbstractAuthzTest extends AbstractKeycloakTest {
@Before
public void onBefore() {
enableFeature(UPLOAD_SCRIPTS);
}
protected AccessToken toAccessToken(String rpt) {
AccessToken accessToken;

View file

@ -96,8 +96,7 @@ public class UserManagedPermissionServiceTest extends AbstractResourceServerTest
.build());
}
@Test
public void testCreate() {
private void testCreate() {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setName("Resource A");
@ -148,7 +147,12 @@ public class UserManagedPermissionServiceTest extends AbstractResourceServerTest
}
@Test
public void testUpdate() {
public void testCreateDeprecatedFeaturesDisabled() {
ProfileAssume.assumeFeatureDisabled(Profile.Feature.UPLOAD_SCRIPTS);
testCreate();
}
private void testUpdate() {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setName("Resource A");
@ -338,8 +342,14 @@ public class UserManagedPermissionServiceTest extends AbstractResourceServerTest
}
@Test
public void testUploadScriptDisabled() {
public void testUpdateDeprecatedFeaturesDisabled() {
ProfileAssume.assumeFeatureDisabled(Profile.Feature.UPLOAD_SCRIPTS);
testUpdate();
}
@Test
public void testUploadScriptDisabled() {
disableFeature(Profile.Feature.UPLOAD_SCRIPTS);
ResourceRepresentation resource = new ResourceRepresentation();
resource.setName("Resource A");

View file

@ -67,7 +67,9 @@ public class ScriptAuthenticatorTest extends AbstractFlowTest {
@BeforeClass
public static void verifyEnvironment() {
// TODO: we should probably enable SCRIPTS automatically when UPLOAD_SCRIPTS is enabled
ProfileAssume.assumeFeatureEnabled(Profile.Feature.SCRIPTS);
ProfileAssume.assumeFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS);
}
@Override

View file

@ -26,6 +26,7 @@ import org.keycloak.admin.client.resource.ClientScopeResource;
import org.keycloak.admin.client.resource.ProtocolMappersResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile;
import org.keycloak.common.util.UriUtils;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.AccountRoles;
@ -45,6 +46,7 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient;
@ -126,6 +128,27 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
testRealms.add(realm);
}
@Test
public void testTokenScriptMapping() {
ProfileAssume.assumeFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS);
{
ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app");
app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper1","computed-via-script", "computed-via-script", "String", true, true, "'hello_' + user.username", false)).close();
app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper2","multiValued-via-script", "multiValued-via-script", "String", true, true, "new java.util.ArrayList(['A','B'])", true)).close();
Response response = app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper3", "syntax-error-script", "syntax-error-script", "String", true, true, "func_tion foo(){ return 'fail';} foo()", false));
assertThat(response.getStatusInfo().getFamily(), is(Response.Status.Family.CLIENT_ERROR));
response.close();
}
{
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
assertEquals("hello_test-user@localhost", accessToken.getOtherClaims().get("computed-via-script"));
assertEquals(Arrays.asList("A","B"), accessToken.getOtherClaims().get("multiValued-via-script"));
}
}
@Test
public void testTokenMapping() throws Exception {
@ -242,9 +265,6 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
Assert.assertNull(accessToken.getResourceAccess("test-app"));
assertTrue(accessToken.getResourceAccess("app").getRoles().contains("hardcoded"));
assertEquals("hello_test-user@localhost", accessToken.getOtherClaims().get("computed-via-script"));
assertEquals(Arrays.asList("A","B"), accessToken.getOtherClaims().get("multiValued-via-script"));
// Assert audiences added through AudienceResolve mapper
Assert.assertThat(accessToken.getAudience(), arrayContainingInAnyOrder( "app", "account"));

View file

@ -0,0 +1,217 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.script;
import static org.junit.Assert.assertFalse;
import static org.keycloak.common.Profile.Feature.SCRIPTS;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
import java.io.IOException;
import javax.ws.rs.core.Response;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.TargetsContainer;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticatorFactory;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.provider.ScriptProviderDescriptor;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.forms.AbstractFlowTest;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.util.ContainerAssume;
import org.keycloak.testsuite.util.ExecutionBuilder;
import org.keycloak.testsuite.util.FlowBuilder;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class DeployedScriptAuthenticatorTest extends AbstractFlowTest {
public static final String EXECUTION_ID = "scriptAuth";
private static final String SCRIPT_DEPLOYMENT_NAME = "scripts.jar";
@Deployment(name = SCRIPT_DEPLOYMENT_NAME, managed = false, testable = false)
@TargetsContainer(AUTH_SERVER_CURRENT)
public static JavaArchive deploy() throws IOException {
ScriptProviderDescriptor representation = new ScriptProviderDescriptor();
representation.addAuthenticator("My Authenticator", "authenticator-a.js");
return ShrinkWrap.create(JavaArchive.class, SCRIPT_DEPLOYMENT_NAME)
.addAsManifestResource(new StringAsset(JsonSerialization.writeValueAsPrettyString(representation)),
"keycloak-scripts.json")
.addAsResource("scripts/authenticator-example.js", "authenticator-a.js");
}
@BeforeClass
public static void verifyEnvironment() {
ContainerAssume.assumeNotAuthServerUndertow();
}
@Rule
public AssertEvents events = new AssertEvents(this);
@Page
protected LoginPage loginPage;
@ArquillianResource
private Deployer deployer;
private AuthenticationFlowRepresentation flow;
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
UserRepresentation failUser = UserBuilder.create()
.id("fail")
.username("fail")
.email("fail@test.com")
.enabled(true)
.password("password")
.build();
UserRepresentation okayUser = UserBuilder.create()
.id("user")
.username("user")
.email("user@test.com")
.enabled(true)
.password("password")
.build();
RealmBuilder.edit(testRealm)
.user(failUser)
.user(okayUser);
}
public void configureFlows() {
deployer.deploy(SCRIPT_DEPLOYMENT_NAME);
if (testContext.isInitialized()) {
return;
}
String scriptFlow = "scriptBrowser";
AuthenticationFlowRepresentation scriptBrowserFlow = FlowBuilder.create()
.alias(scriptFlow)
.description("dummy pass through registration")
.providerId("basic-flow")
.topLevel(true)
.builtIn(false)
.build();
Response createFlowResponse = testRealm().flows().createFlow(scriptBrowserFlow);
Assert.assertEquals(201, createFlowResponse.getStatus());
RealmRepresentation realm = testRealm().toRepresentation();
realm.setBrowserFlow(scriptFlow);
realm.setDirectGrantFlow(scriptFlow);
testRealm().update(realm);
this.flow = findFlowByAlias(scriptFlow);
AuthenticationExecutionRepresentation usernamePasswordFormExecution = ExecutionBuilder.create()
.id("username password form")
.parentFlow(this.flow.getId())
.requirement(AuthenticationExecutionModel.Requirement.REQUIRED.name())
.authenticator(UsernamePasswordFormFactory.PROVIDER_ID)
.build();
AuthenticationExecutionRepresentation authScriptExecution = ExecutionBuilder.create()
.id(EXECUTION_ID)
.parentFlow(this.flow.getId())
.requirement(AuthenticationExecutionModel.Requirement.REQUIRED.name())
.authenticator("script-authenticator-a.js")
.build();
Response addExecutionResponse = testRealm().flows().addExecution(usernamePasswordFormExecution);
Assert.assertEquals(201, addExecutionResponse.getStatus());
addExecutionResponse.close();
addExecutionResponse = testRealm().flows().addExecution(authScriptExecution);
Assert.assertEquals(201, addExecutionResponse.getStatus());
addExecutionResponse.close();
testContext.setInitialized(true);
}
@After
public void onAfter() {
deployer.undeploy(SCRIPT_DEPLOYMENT_NAME);
}
/**
* KEYCLOAK-3491
*/
@Test
public void loginShouldWorkWithScriptAuthenticator() {
ProfileAssume.assumeFeatureEnabled(SCRIPTS);
configureFlows();
loginPage.open();
loginPage.login("user", "password");
events.expectLogin().user("user").detail(Details.USERNAME, "user").assertEvent();
}
/**
* KEYCLOAK-3491
*/
@Test
public void loginShouldFailWithScriptAuthenticator() {
ProfileAssume.assumeFeatureEnabled(SCRIPTS);
configureFlows();
loginPage.open();
loginPage.login("fail", "password");
events.expect(EventType.LOGIN_ERROR).user((String) null).error(Errors.USER_NOT_FOUND).assertEvent();
}
@Test
public void testScriptAuthenticatorNotAvailable() {
ProfileAssume.assumeFeatureDisabled(UPLOAD_SCRIPTS);
assertFalse(testRealm().flows().getAuthenticatorProviders().stream().anyMatch(
provider -> ScriptBasedAuthenticatorFactory.PROVIDER_ID.equals(provider.get("id"))));
}
}

View file

@ -0,0 +1,129 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.script;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId;
import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
import static org.keycloak.testsuite.util.ProtocolMapperUtil.createScriptMapper;
import java.io.IOException;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.TargetsContainer;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.common.Profile;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.ScriptBasedOIDCProtocolMapper;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.provider.ScriptProviderDescriptor;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.util.ContainerAssume;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class DeployedScriptMapperTest extends AbstractTestRealmKeycloakTest {
private static final String SCRIPT_DEPLOYMENT_NAME = "scripts.jar";
@Deployment(name = SCRIPT_DEPLOYMENT_NAME, managed = false, testable = false)
@TargetsContainer(AUTH_SERVER_CURRENT)
public static JavaArchive deploy() throws IOException {
ScriptProviderDescriptor representation = new ScriptProviderDescriptor();
representation.addMapper("My Mapper", "mapper-a.js");
return ShrinkWrap.create(JavaArchive.class, SCRIPT_DEPLOYMENT_NAME)
.addAsManifestResource(new StringAsset(JsonSerialization.writeValueAsPrettyString(representation)),
"keycloak-scripts.json")
.addAsResource("scripts/mapper-example.js", "mapper-a.js");
}
@BeforeClass
public static void verifyEnvironment() {
ContainerAssume.assumeNotAuthServerUndertow();
}
@ArquillianResource
private Deployer deployer;
@Before
public void configureFlows() {
deployer.deploy(SCRIPT_DEPLOYMENT_NAME);
}
@After
public void onAfter() {
deployer.undeploy(SCRIPT_DEPLOYMENT_NAME);
}
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
}
@Test
public void testScriptMapperNotAvailable() {
ProfileAssume.assumeFeatureDisabled(UPLOAD_SCRIPTS);
assertFalse(adminClient.serverInfo().getInfo().getProtocolMapperTypes().get(OIDCLoginProtocol.LOGIN_PROTOCOL).stream()
.anyMatch(
mapper -> ScriptBasedOIDCProtocolMapper.PROVIDER_ID.equals(mapper.getId())));
}
@Test
public void testTokenScriptMapping() {
ProfileAssume.assumeFeatureEnabled(Profile.Feature.SCRIPTS);
{
ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app");
ProtocolMapperRepresentation mapper = createScriptMapper("test-script-mapper1", "computed-via-script",
"computed-via-script", "String", true, true, "'hello_' + user.username", false);
mapper.setProtocolMapper("script-mapper-a.js");
app.getProtocolMappers().createMapper(mapper).close();
}
{
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
assertEquals("hello_test-user@localhost", accessToken.getOtherClaims().get("computed-via-script"));
}
}
private OAuthClient.AccessTokenResponse browserLogin(String clientSecret, String username, String password) {
OAuthClient.AuthorizationEndpointResponse authzEndpointResponse = oauth.doLogin(username, password);
return oauth.doAccessTokenRequest(authzEndpointResponse.getCode(), clientSecret);
}
}

View file

@ -0,0 +1,214 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.script;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.List;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.TargetsContainer;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.PermissionsResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.DecisionEffect;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyEvaluationRequest;
import org.keycloak.representations.idm.authorization.PolicyEvaluationResponse;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.provider.ScriptProviderDescriptor;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
import org.keycloak.testsuite.authz.AbstractAuthzTest;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.ContainerAssume;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.RoleBuilder;
import org.keycloak.testsuite.util.RolesBuilder;
import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class DeployedScriptPolicyTest extends AbstractAuthzTest {
private static final String SCRIPT_DEPLOYMENT_NAME = "scripts.jar";
@Deployment(name = SCRIPT_DEPLOYMENT_NAME, managed = false, testable = false)
@TargetsContainer(AUTH_SERVER_CURRENT)
public static JavaArchive deploy() throws IOException {
ScriptProviderDescriptor representation = new ScriptProviderDescriptor();
representation.addPolicy("Grant Policy", "policy-grant.js");
representation.addPolicy("Deny Policy", "policy-deny.js");
return ShrinkWrap.create(JavaArchive.class, SCRIPT_DEPLOYMENT_NAME)
.addAsManifestResource(new StringAsset(JsonSerialization.writeValueAsPrettyString(representation)),
"keycloak-scripts.json")
.addAsResource(new StringAsset("$evaluation.grant();"), "policy-grant.js")
.addAsResource(new StringAsset("$evaluation.deny();"), "policy-deny.js");
}
@BeforeClass
public static void verifyEnvironment() {
ContainerAssume.assumeNotAuthServerUndertow();
}
@ArquillianResource
private Deployer deployer;
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
testRealms.add(RealmBuilder.create().name("authz-test")
.roles(RolesBuilder.create().realmRole(RoleBuilder.create().name("uma_authorization").build()))
.user(UserBuilder.create().username("marta").password("password").addRoles("uma_authorization"))
.user(UserBuilder.create().username("kolo").password("password"))
.client(ClientBuilder.create().clientId("resource-server")
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/resource-server-test")
.defaultRoles("uma_protection")
.directAccessGrants())
.build());
}
@Before
public void onBefore() {
deployer.deploy(SCRIPT_DEPLOYMENT_NAME);
AuthorizationResource authorization = getAuthorizationResource();
authorization.resources().create(new ResourceRepresentation("Default Resource"));
}
@After
public void onAfter() {
deployer.undeploy(SCRIPT_DEPLOYMENT_NAME);
}
@Test
public void testJSPolicyProviderNotAvailable() {
ProfileAssume.assumeFeatureDisabled(UPLOAD_SCRIPTS);
assertFalse(getAuthorizationResource().policies().policyProviders().stream().anyMatch(rep -> "js".equals(rep.getType())));
}
@Test
@UncaughtServerErrorExpected
public void failCreateJSPolicy() {
ProfileAssume.assumeFeatureDisabled(UPLOAD_SCRIPTS);
JSPolicyRepresentation grantPolicy = new JSPolicyRepresentation();
grantPolicy.setName("JS Policy");
grantPolicy.setType("js");
grantPolicy.setCode("$evaluation.grant();");
try (Response response = getAuthorizationResource().policies().js().create(grantPolicy)) {
assertEquals(500, response.getStatus());
}
}
@Test
public void testCreatePermission() {
AuthorizationResource authorization = getAuthorizationResource();
PolicyRepresentation grantPolicy = new PolicyRepresentation();
grantPolicy.setName("Grant Policy");
grantPolicy.setType("script-policy-grant.js");
authorization.policies().create(grantPolicy).close();
PolicyRepresentation denyPolicy = new PolicyRepresentation();
denyPolicy.setName("Deny Policy");
denyPolicy.setType("script-policy-deny.js");
authorization.policies().create(denyPolicy).close();
PermissionsResource permissions = authorization.permissions();
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
permission.setName("Test Deployed JS Permission");
permission.addResource("Default Resource");
permission.addPolicy(grantPolicy.getName());
permissions.resource().create(permission).close();
PolicyEvaluationRequest request = new PolicyEvaluationRequest();
request.setUserId("marta");
request.addResource("Default Resource");
PolicyEvaluationResponse response = authorization.policies().evaluate(request);
assertEquals(DecisionEffect.PERMIT, response.getStatus());
permission = permissions.resource().findByName(permission.getName());
permission.addPolicy(denyPolicy.getName());
permissions.resource().findById(permission.getId()).update(permission);
response = authorization.policies().evaluate(request);
assertEquals(DecisionEffect.DENY, response.getStatus());
permission.addPolicy(grantPolicy.getName());
permissions.resource().findById(permission.getId()).update(permission);
response = authorization.policies().evaluate(request);
assertEquals(DecisionEffect.DENY, response.getStatus());
permission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
permissions.resource().findById(permission.getId()).update(permission);
response = authorization.policies().evaluate(request);
assertEquals(DecisionEffect.PERMIT, response.getStatus());
}
private AuthorizationResource getAuthorizationResource() {
return getClient(realmsResouce().realm("authz-test"), "resource-server").authorization();
}
private ClientResource getClient(RealmResource realm, String clientId) {
ClientsResource clients = realm.clients();
return clients.findByClientId(clientId).stream().map(representation -> clients.get(representation.getId())).findFirst()
.orElseThrow(() -> new RuntimeException("Expected client [resource-server-test]"));
}
}

View file

@ -0,0 +1 @@
'hello_' + user.username

View file

@ -19,6 +19,7 @@ package org.keycloak.testsuite.console.authorization;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import java.util.UUID;
@ -52,6 +53,11 @@ import org.keycloak.testsuite.util.UserBuilder;
*/
public class AggregatePolicyManagementTest extends AbstractAuthorizationSettingsTest {
@Before
public void onBefore() {
enableFeature(UPLOAD_SCRIPTS);
}
@Before
public void configureTest() {
super.configureTest();

View file

@ -18,7 +18,11 @@ package org.keycloak.testsuite.console.authorization;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import javax.ws.rs.core.Response;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.idm.authorization.Logic;
@ -29,6 +33,11 @@ import org.keycloak.testsuite.console.page.clients.authorization.policy.JSPolicy
*/
public class JSPolicyManagementTest extends AbstractAuthorizationSettingsTest {
@Before
public void onBefore() {
enableFeature(UPLOAD_SCRIPTS);
}
@Test
public void testUpdate() throws InterruptedException {
authorizationPage.navigateTo();

View file

@ -354,6 +354,9 @@ module.config(['$routeProvider', function ($routeProvider) {
},
client : function(ClientLoader) {
return ClientLoader();
},
serverInfo : function(ServerInfoLoader) {
return ServerInfoLoader();
}
},
controller: 'ResourceServerPolicyJSDetailCtrl'
@ -365,6 +368,9 @@ module.config(['$routeProvider', function ($routeProvider) {
},
client : function(ClientLoader) {
return ClientLoader();
},
serverInfo : function(ServerInfoLoader) {
return ServerInfoLoader();
}
},
controller: 'ResourceServerPolicyJSDetailCtrl'

View file

@ -717,8 +717,15 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l
});
$scope.addPolicy = function(policyType) {
if (policyType.type.endsWith('.js')) {
ResourceServerPolicy.save({realm : realm.realm, client : client.id, type: policyType.type}, {name: policyType.name, type: policyType.type}, function(data) {
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/");
Notifications.success("The policy has been created.");
});
} else {
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/" + policyType.type + "/create");
}
}
$scope.firstPage = function() {
$scope.query.first = 0;
@ -1953,15 +1960,17 @@ module.controller('ResourceServerPolicyGroupDetailCtrl', function($scope, $route
}, realm, client, $scope);
});
module.controller('ResourceServerPolicyJSDetailCtrl', function($scope, $route, $location, realm, PolicyController, client) {
module.controller('ResourceServerPolicyJSDetailCtrl', function($scope, $route, $location, realm, PolicyController, client, serverInfo) {
PolicyController.onInit({
getPolicyType : function() {
return "js";
},
onInit : function() {
$scope.readOnly = !serverInfo.featureEnabled('UPLOAD_SCRIPTS');
$scope.initEditor = function(editor){
editor.$blockScrolling = Infinity;
editor.setReadOnly($scope.readOnly);
var session = editor.getSession();
session.setMode('ace/mode/javascript');
};

View file

@ -23,14 +23,14 @@
<div class="form-group">
<label class="col-md-2 control-label" for="name">{{:: 'name' | translate}} <span class="required">*</span></label>
<div class="col-sm-6">
<input class="form-control" type="text" id="name" name="name" data-ng-model="policy.name" autofocus required data-ng-blur="checkNewNameAvailability()">
<input class="form-control" type="text" id="name" name="name" data-ng-model="policy.name" autofocus required data-ng-blur="checkNewNameAvailability()" data-ng-disabled="readOnly">
</div>
<kc-tooltip>{{:: 'authz-policy-name.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="description">{{:: 'description' | translate}} </label>
<div class="col-sm-6">
<input class="form-control" type="text" id="description" name="description" data-ng-model="policy.description">
<input class="form-control" type="text" id="description" name="description" data-ng-model="policy.description" data-ng-disabled="readOnly">
</div>
<kc-tooltip>{{:: 'authz-policy-description.tooltip' | translate}}</kc-tooltip>
</div>
@ -46,7 +46,7 @@
<div class="col-sm-1">
<select class="form-control" id="logic"
data-ng-model="policy.logic">
data-ng-model="policy.logic" data-ng-disabled="readOnly">
<option value="POSITIVE">{{:: 'authz-policy-logic-positive' | translate}}</option>
<option value="NEGATIVE">{{:: 'authz-policy-logic-negative' | translate}}</option>
</select>

View file

@ -76,7 +76,7 @@
<span ng-if="!policy.details || !policy.details.loaded" class="fa fa-angle-right"></span>
<span ng-if="policy.details && policy.details.loaded" class="fa fa-angle-right fa-angle-down"></span>
</td>
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy/{{policy.type}}/{{policy.id}}">{{policy.name}}</a></td>
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy/{{policy.type.endsWith('.js') ? 'js': policy.type}}/{{policy.id}}">{{policy.name}}</a></td>
<td>{{policy.description}}</td>
<td>{{policy.type}}</td>
<td align="center">

View file

@ -111,6 +111,16 @@
<artifactId>keycloak-server-spi-private</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authz-policy-common</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.wildfly.core</groupId>

View file

@ -48,6 +48,9 @@ public class KeycloakProviderDeploymentProcessor implements DeploymentUnitProces
}
KeycloakDeploymentInfo info = KeycloakProviderDependencyProcessor.getKeycloakProviderDeploymentInfo(deploymentUnit);
ScriptProviderDeploymentProcessor.deploy(deploymentUnit, info);
if (info.isProvider()) {
logger.infov("Deploying Keycloak provider: {0}", deploymentUnit.getName());
final Module module = deploymentUnit.getAttachment(Attachments.MODULE);

View file

@ -0,0 +1,133 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.subsystem.server.extension;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.AUTHENTICATORS;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.MAPPERS;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.POLICIES;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.module.ResourceRoot;
import org.jboss.vfs.VirtualFile;
import org.keycloak.authentication.AuthenticatorSpi;
import org.keycloak.authentication.authenticators.browser.DeployedScriptAuthenticatorFactory;
import org.keycloak.authorization.policy.provider.PolicySpi;
import org.keycloak.authorization.policy.provider.js.DeployedScriptPolicyFactory;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.protocol.ProtocolMapperSpi;
import org.keycloak.protocol.oidc.mappers.DeployedScriptOIDCProtocolMapper;
import org.keycloak.provider.KeycloakDeploymentInfo;
import org.keycloak.representations.provider.ScriptProviderDescriptor;
import org.keycloak.representations.provider.ScriptProviderMetadata;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
final class ScriptProviderDeploymentProcessor {
private static final Map<String, BiConsumer<KeycloakDeploymentInfo, ScriptProviderMetadata>> PROVIDERS = new HashMap<>();
private static void registerScriptAuthenticator(KeycloakDeploymentInfo info, ScriptProviderMetadata metadata) {
info.addProvider(AuthenticatorSpi.class, new DeployedScriptAuthenticatorFactory(metadata));
}
private static void registerScriptPolicy(KeycloakDeploymentInfo info, ScriptProviderMetadata metadata) {
info.addProvider(PolicySpi.class, new DeployedScriptPolicyFactory(metadata));
}
private static void registerScriptMapper(KeycloakDeploymentInfo info, ScriptProviderMetadata metadata) {
info.addProvider(ProtocolMapperSpi.class, new DeployedScriptOIDCProtocolMapper(metadata));
}
static void deploy(DeploymentUnit deploymentUnit, KeycloakDeploymentInfo info) {
ResourceRoot resourceRoot = deploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT);
if (resourceRoot == null) {
return;
}
VirtualFile jarFile = resourceRoot.getRoot();
if (jarFile == null || !jarFile.exists() || !jarFile.getName().endsWith(".jar")) {
return;
}
ScriptProviderDescriptor descriptor = readScriptProviderDescriptor(jarFile);
if (descriptor == null) {
return;
}
for (Map.Entry<String, List<ScriptProviderMetadata>> entry : descriptor.getProviders().entrySet()) {
for (ScriptProviderMetadata metadata : entry.getValue()) {
String fileName = metadata.getFileName();
if (fileName == null) {
throw new RuntimeException("You must provide the script file name");
}
try (InputStream in = jarFile.getChild(fileName).openStream()) {
metadata.setCode(StreamUtil.readString(in, StandardCharsets.UTF_8));
} catch (IOException cause) {
throw new RuntimeException("Failed to read script file [" + fileName + "]", cause);
}
metadata.setId(new StringBuilder("script").append("-").append(fileName).toString());
String name = metadata.getName();
if (name == null) {
name = fileName;
}
metadata.setName(name);
PROVIDERS.get(entry.getKey()).accept(info, metadata);
}
}
}
private static ScriptProviderDescriptor readScriptProviderDescriptor(VirtualFile deploymentRoot) {
VirtualFile metadataFile = deploymentRoot.getChild("META-INF/keycloak-scripts.json");
if (!metadataFile.exists()) {
return null;
}
try (InputStream inputStream = metadataFile.openStream()) {
return JsonSerialization.readValue(inputStream, ScriptProviderDescriptor.class);
} catch (IOException cause) {
throw new RuntimeException("Failed to read providers metadata", cause);
}
}
static {
PROVIDERS.put(AUTHENTICATORS, ScriptProviderDeploymentProcessor::registerScriptAuthenticator);
PROVIDERS.put(POLICIES, ScriptProviderDeploymentProcessor::registerScriptPolicy);
PROVIDERS.put(MAPPERS, ScriptProviderDeploymentProcessor::registerScriptMapper);
}
}