[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.model.Policy;
import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.common.Profile;
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;
@ -56,17 +57,17 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory<JSPolicyRe
@Override @Override
public void onCreate(Policy policy, JSPolicyRepresentation representation, AuthorizationProvider authorization) { public void onCreate(Policy policy, JSPolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation.getCode()); updatePolicy(policy, representation.getCode(), authorization);
} }
@Override @Override
public void onUpdate(Policy policy, JSPolicyRepresentation representation, AuthorizationProvider authorization) { public void onUpdate(Policy policy, JSPolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation.getCode()); updatePolicy(policy, representation.getCode(), authorization);
} }
@Override @Override
public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) { public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation.getConfig().get("code")); updatePolicy(policy, representation.getConfig().get("code"), authorization);
} }
@Override @Override
@ -96,6 +97,11 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory<JSPolicyRe
return "js"; return "js";
} }
@Override
public boolean isInternal() {
return !Profile.isFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS);
}
private EvaluatableScriptAdapter getEvaluatableScript(final AuthorizationProvider authz, final Policy policy) { private EvaluatableScriptAdapter getEvaluatableScript(final AuthorizationProvider authz, final Policy policy) {
return scriptCache.computeIfAbsent(policy.getId(), id -> { return scriptCache.computeIfAbsent(policy.getId(), id -> {
final ScriptingProvider scripting = authz.getKeycloakSession().getProvider(ScriptingProvider.class); 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 scriptName = policy.getName();
String scriptCode = policy.getConfig().get("code"); String scriptCode = policy.getConfig().get("code");
String scriptDescription = policy.getDescription(); 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); 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()); 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); policy.putConfig("code", code);
} }
protected boolean isDeployed() {
return false;
}
} }

View file

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

View file

@ -110,6 +110,11 @@ public class Profile {
disabledFeatures.add(f); disabledFeatures.add(f);
} else if (DEPRECATED.equals(f.getType())) { } else if (DEPRECATED.equals(f.getType())) {
logger.warnf("Deprecated feature enabled: " + f.name().toLowerCase()); 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; break;
case PREVIEW: case PREVIEW:

View file

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

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-services"/>
<module name="org.keycloak.keycloak-server-spi-private"/> <module name="org.keycloak.keycloak-server-spi-private"/>
<module name="org.keycloak.keycloak-wildfly-adapter" optional="true"/> <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"/> <module name="org.jboss.metadata"/>
</dependencies> </dependencies>
</module> </module>

View file

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

View file

@ -1,14 +1,20 @@
package org.keycloak.provider; package org.keycloak.provider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class KeycloakDeploymentInfo { public class KeycloakDeploymentInfo {
private String name; private String name;
private boolean services; private boolean services;
private boolean themes; private boolean themes;
private boolean themeResources; private boolean themeResources;
private Map<Class<? extends Spi>, List<ProviderFactory>> providers = new HashMap<>();
public boolean isProvider() { public boolean isProvider() {
return services || themes || themeResources; return services || themes || themeResources || !providers.isEmpty();
} }
public boolean hasServices() { public boolean hasServices() {
@ -53,4 +59,12 @@ public class KeycloakDeploymentInfo {
themeResources = true; themeResources = true;
return this; 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.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError; import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator; import org.keycloak.authentication.Authenticator;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.ScriptModel; import org.keycloak.models.ScriptModel;
@ -132,15 +133,21 @@ public class ScriptBasedAuthenticator implements Authenticator {
} }
private boolean hasAuthenticatorConfig(AuthenticationFlowContext context) { private boolean hasAuthenticatorConfig(AuthenticationFlowContext context) {
return context != null if (context == null)
&& context.getAuthenticatorConfig() != null return false;
&& context.getAuthenticatorConfig().getConfig() != null AuthenticatorConfigModel config = getAuthenticatorConfig(context);
&& !context.getAuthenticatorConfig().getConfig().isEmpty(); return config != null
&& config.getConfig() != null
&& !config.getConfig().isEmpty();
}
protected AuthenticatorConfigModel getAuthenticatorConfig(AuthenticationFlowContext context) {
return context.getAuthenticatorConfig();
} }
private InvocableScriptAdapter getInvocableScriptAdapter(AuthenticationFlowContext context) { 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 scriptName = config.get(SCRIPT_NAME);
String scriptCode = config.get(SCRIPT_CODE); String scriptCode = config.get(SCRIPT_CODE);

View file

@ -153,6 +153,6 @@ public class ScriptBasedAuthenticatorFactory implements AuthenticatorFactory, En
@Override @Override
public boolean isSupported() { 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; package org.keycloak.authorization;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator; import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
import org.keycloak.authorization.policy.evaluation.PolicyEvaluator; 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.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderFactory;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
public class DefaultAuthorizationProviderFactory implements AuthorizationProviderFactory { public class DefaultAuthorizationProviderFactory implements AuthorizationProviderFactory {
private Map<String, PolicyProviderFactory> policyProviderFactories;
private PolicyEvaluator policyEvaluator = new DefaultPolicyEvaluator(); private PolicyEvaluator policyEvaluator = new DefaultPolicyEvaluator();
@Override @Override
@ -51,7 +43,6 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide
@Override @Override
public void postInit(KeycloakSessionFactory factory) { public void postInit(KeycloakSessionFactory factory) {
policyProviderFactories = configurePolicyProviderFactories(factory);
} }
@Override @Override
@ -66,21 +57,6 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide
@Override @Override
public AuthorizationProvider create(KeycloakSession session, RealmModel realm) { 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(); PolicyStore policyStore = storeFactory.getPolicyStore();
PolicyProviderFactory resource = getProviderFactory(policy.getType()); PolicyProviderFactory resource = getProviderFactory(policy.getType());
resource.onRemove(policy, authorization); if (resource != null) {
resource.onRemove(policy, authorization);
}
policyStore.delete(policy.getId()); policyStore.delete(policy.getId());

View file

@ -206,6 +206,8 @@ public class ResourceServerService {
defaultPolicy.setConfig(defaultPolicyConfig); defaultPolicy.setConfig(defaultPolicyConfig);
session.setAttribute("ALLOW_CREATE_POLICY", true);
getPolicyResource().create(defaultPolicy); getPolicyResource().create(defaultPolicy);
return 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.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperConfigException; import org.keycloak.protocol.ProtocolMapperConfigException;
import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder; import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
@ -43,7 +44,8 @@ import java.util.List;
* *
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a> * @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"; 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."; return "Evaluates a JavaScript function to produce a token claim based on context information.";
} }
@Override
public boolean isSupported() { public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS); return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS) && Profile.isFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS);
} }
@Override @Override
@ -128,7 +131,7 @@ public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper im
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) { protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) {
UserModel user = userSession.getUser(); UserModel user = userSession.getUser();
String scriptSource = mappingModel.getConfig().get(SCRIPT); String scriptSource = getScriptCode(mappingModel);
RealmModel realm = userSession.getRealm(); RealmModel realm = userSession.getRealm();
ScriptingProvider scripting = keycloakSession.getProvider(ScriptingProvider.class); ScriptingProvider scripting = keycloakSession.getProvider(ScriptingProvider.class);
@ -156,7 +159,7 @@ public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper im
@Override @Override
public void validateConfig(KeycloakSession session, RealmModel realm, ProtocolMapperContainerModel client, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException { 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) { if (scriptCode == null) {
return; 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, public static ProtocolMapperModel create(String name,
String userAttribute, String userAttribute,
String tokenClaimName, String claimType, 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); logger.debugv("Provider loaders {0}", factories);
loaders.add(new DefaultProviderLoader(info, baseClassLoader)); loaders.add(new DefaultProviderLoader(info, baseClassLoader));
loaders.add(new DeploymentProviderLoader(info));
if (resources != null) { if (resources != null) {
for (String r : resources) { for (String r : resources) {

View file

@ -77,6 +77,7 @@ public class ProfileAssume {
} }
private static boolean isFeatureEnabled(Profile.Feature feature) { private static boolean isFeatureEnabled(Profile.Feature feature) {
updateProfile();
return !disabledFeatures.contains(feature.name()); 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.RealmsResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource; import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.common.Profile;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
@ -61,6 +62,7 @@ import org.keycloak.testsuite.util.TestEventsLogger;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import javax.ws.rs.NotFoundException; import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import java.io.IOException; import java.io.IOException;
@ -81,6 +83,7 @@ import java.util.stream.Collectors;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.admin.Users.setPasswordFor; import static org.keycloak.testsuite.admin.Users.setPasswordFor;
import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN; import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN;
@ -149,6 +152,7 @@ public abstract class AbstractKeycloakTest {
private PropertiesConfiguration constantsProperties; private PropertiesConfiguration constantsProperties;
private boolean resetTimeOffset; private boolean resetTimeOffset;
private List<Profile.Feature> enabledFeatures = new ArrayList<>();
@Before @Before
public void beforeAbstractKeycloakTest() throws Exception { public void beforeAbstractKeycloakTest() throws Exception {
@ -226,6 +230,10 @@ public abstract class AbstractKeycloakTest {
testContext.getCleanups().clear(); testContext.getCleanups().clear();
} }
for (Profile.Feature feature : enabledFeatures) {
disableFeature(feature);
}
postAfterAbstractKeycloak(); postAfterAbstractKeycloak();
// Remove all browsers from queue // Remove all browsers from queue
@ -631,4 +639,17 @@ public abstract class AbstractKeycloakTest {
} }
return in; 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.hamcrest.Matchers.is;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; 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.URLAssert.assertCurrentUrlStartsWith;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad; 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.loadJson;
@ -164,6 +165,7 @@ public abstract class AbstractBasePhotozExampleAdapterTest extends AbstractPhoto
@Override @Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) { public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
enableFeature(UPLOAD_SCRIPTS);
RealmRepresentation realm = loadRealm(new File(TEST_APPS_HOME_DIR + "/photoz/photoz-realm.json")); RealmRepresentation realm = loadRealm(new File(TEST_APPS_HOME_DIR + "/photoz/photoz-realm.json"));
realm.setAccessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY); // seconds 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.container.test.api.Deployer;
import org.jboss.arquillian.test.api.ArquillianResource; 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.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource; 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.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest; import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.util.UIUtils; import org.keycloak.testsuite.util.UIUtils;
import org.openqa.selenium.By; import org.openqa.selenium.By;
@ -42,6 +41,7 @@ import java.net.URL;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertFalse; 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.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.utils.io.IOUtil.loadJson; import static org.keycloak.testsuite.utils.io.IOUtil.loadJson;
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm; import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm;
@ -58,6 +58,11 @@ public abstract class AbstractBaseServletAuthzAdapterTest extends AbstractExampl
@ArquillianResource @ArquillianResource
private Deployer deployer; private Deployer deployer;
@Before
public void onBefore() {
enableFeature(UPLOAD_SCRIPTS);
}
@Override @Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) { public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
testRealms.add( 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.assertFalse;
import static org.junit.Assert.assertTrue; 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.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm; 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.container.test.api.Deployment;
import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource; import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource; 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.RealmRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest; import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants; import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
@ -69,6 +72,7 @@ public class ServletPolicyEnforcerTest extends AbstractExampleAdapterTest {
@Override @Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) { public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
enableFeature(UPLOAD_SCRIPTS);
testRealms.add( testRealms.add(
loadRealm(new File(TEST_APPS_HOME_DIR + "/servlet-policy-enforcer/servlet-policy-enforcer-authz-realm.json"))); 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.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.common.Profile;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest; import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.adapter.page.AngularCorsProductTestApp; import org.keycloak.testsuite.adapter.page.AngularCorsProductTestApp;
@ -108,6 +109,7 @@ public class CorsExampleAdapterTest extends AbstractExampleAdapterTest {
@Override @Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) { public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
enableFeature(Profile.Feature.UPLOAD_SCRIPTS);
testRealms.add( testRealms.add(
loadRealm(new File(TEST_APPS_HOME_DIR + "/cors/cors-realm.json"))); 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.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation; 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.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation;
@ -957,13 +958,10 @@ public class PermissionsTest extends AbstractKeycloakTest {
invoke(new InvocationWithResponse() { invoke(new InvocationWithResponse() {
public void invoke(RealmResource realm, AtomicReference<Response> response) { public void invoke(RealmResource realm, AtomicReference<Response> response) {
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
PolicyRepresentation representation = new PolicyRepresentation(); ResourcePermissionRepresentation representation = new ResourcePermissionRepresentation();
representation.setName("Test PermissionsTest"); representation.setName("Test PermissionsTest");
representation.setType("js"); representation.addResource("Default Resource");
HashMap<String, String> config = new HashMap<>(); response.set(authorization.permissions().resource().create(representation));
config.put("code", "");
representation.setConfig(config);
response.set(authorization.policies().create(representation));
} }
}, AUTHORIZATION, true); }, AUTHORIZATION, true);
invoke(new Invocation() { invoke(new Invocation() {

View file

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

View file

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

View file

@ -35,6 +35,7 @@ import java.util.stream.Collectors;
import javax.security.cert.X509Certificate; import javax.security.cert.X509Certificate;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.AuthorizationContext; import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext; 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.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource; import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.authorization.client.AuthzClient; import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.common.Profile;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
@ -108,6 +110,11 @@ public class PolicyEnforcerClaimsTest extends AbstractKeycloakTest {
.build()); .build());
} }
@Before
public void onBefore() {
enableFeature(Profile.Feature.UPLOAD_SCRIPTS);
}
@Test @Test
public void testEnforceUMAAccessWithClaimsUsingBearerToken() { public void testEnforceUMAAccessWithClaimsUsingBearerToken() {
initAuthorizationSettings(getClientResource("resource-server-uma-test")); 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.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import javax.security.cert.X509Certificate; import javax.security.cert.X509Certificate;
import javax.ws.rs.HttpMethod; import javax.ws.rs.HttpMethod;
@ -126,6 +127,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
@Before @Before
public void onBefore() { public void onBefore() {
enableFeature(UPLOAD_SCRIPTS);
initAuthorizationSettings(getClientResource(RESOURCE_SERVER_CLIENT_ID)); initAuthorizationSettings(getClientResource(RESOURCE_SERVER_CLIENT_ID));
} }

View file

@ -1,17 +1,23 @@
package org.keycloak.testsuite.authz; 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.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.ProfileAssume;
/** /**
* @author mhajas * @author mhajas
*/ */
public abstract class AbstractAuthzTest extends AbstractKeycloakTest { public abstract class AbstractAuthzTest extends AbstractKeycloakTest {
@Before
public void onBefore() {
enableFeature(UPLOAD_SCRIPTS);
}
protected AccessToken toAccessToken(String rpt) { protected AccessToken toAccessToken(String rpt) {
AccessToken accessToken; AccessToken accessToken;

View file

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

View file

@ -67,7 +67,9 @@ public class ScriptAuthenticatorTest extends AbstractFlowTest {
@BeforeClass @BeforeClass
public static void verifyEnvironment() { 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.SCRIPTS);
ProfileAssume.assumeFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS);
} }
@Override @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.ProtocolMappersResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.AccountRoles; import org.keycloak.models.AccountRoles;
@ -45,6 +46,7 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.ClientManager; import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
@ -126,6 +128,27 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
testRealms.add(realm); 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 @Test
public void testTokenMapping() throws Exception { public void testTokenMapping() throws Exception {
@ -242,9 +265,6 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
Assert.assertNull(accessToken.getResourceAccess("test-app")); Assert.assertNull(accessToken.getResourceAccess("test-app"));
assertTrue(accessToken.getResourceAccess("app").getRoles().contains("hardcoded")); 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 audiences added through AudienceResolve mapper
Assert.assertThat(accessToken.getAudience(), arrayContainingInAnyOrder( "app", "account")); 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.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import java.util.UUID; import java.util.UUID;
@ -52,6 +53,11 @@ import org.keycloak.testsuite.util.UserBuilder;
*/ */
public class AggregatePolicyManagementTest extends AbstractAuthorizationSettingsTest { public class AggregatePolicyManagementTest extends AbstractAuthorizationSettingsTest {
@Before
public void onBefore() {
enableFeature(UPLOAD_SCRIPTS);
}
@Before @Before
public void configureTest() { public void configureTest() {
super.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.assertEquals;
import static org.junit.Assert.assertNull; 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.junit.Test;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation; import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.idm.authorization.Logic; 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 { public class JSPolicyManagementTest extends AbstractAuthorizationSettingsTest {
@Before
public void onBefore() {
enableFeature(UPLOAD_SCRIPTS);
}
@Test @Test
public void testUpdate() throws InterruptedException { public void testUpdate() throws InterruptedException {
authorizationPage.navigateTo(); authorizationPage.navigateTo();

View file

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

View file

@ -717,7 +717,14 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l
}); });
$scope.addPolicy = function(policyType) { $scope.addPolicy = function(policyType) {
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/" + policyType.type + "/create"); 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.firstPage = function() {
@ -1953,15 +1960,17 @@ module.controller('ResourceServerPolicyGroupDetailCtrl', function($scope, $route
}, realm, client, $scope); }, 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({ PolicyController.onInit({
getPolicyType : function() { getPolicyType : function() {
return "js"; return "js";
}, },
onInit : function() { onInit : function() {
$scope.readOnly = !serverInfo.featureEnabled('UPLOAD_SCRIPTS');
$scope.initEditor = function(editor){ $scope.initEditor = function(editor){
editor.$blockScrolling = Infinity; editor.$blockScrolling = Infinity;
editor.setReadOnly($scope.readOnly);
var session = editor.getSession(); var session = editor.getSession();
session.setMode('ace/mode/javascript'); session.setMode('ace/mode/javascript');
}; };

View file

@ -23,14 +23,14 @@
<div class="form-group"> <div class="form-group">
<label class="col-md-2 control-label" for="name">{{:: 'name' | translate}} <span class="required">*</span></label> <label class="col-md-2 control-label" for="name">{{:: 'name' | translate}} <span class="required">*</span></label>
<div class="col-sm-6"> <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> </div>
<kc-tooltip>{{:: 'authz-policy-name.tooltip' | translate}}</kc-tooltip> <kc-tooltip>{{:: 'authz-policy-name.tooltip' | translate}}</kc-tooltip>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-md-2 control-label" for="description">{{:: 'description' | translate}} </label> <label class="col-md-2 control-label" for="description">{{:: 'description' | translate}} </label>
<div class="col-sm-6"> <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> </div>
<kc-tooltip>{{:: 'authz-policy-description.tooltip' | translate}}</kc-tooltip> <kc-tooltip>{{:: 'authz-policy-description.tooltip' | translate}}</kc-tooltip>
</div> </div>
@ -46,7 +46,7 @@
<div class="col-sm-1"> <div class="col-sm-1">
<select class="form-control" id="logic" <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="POSITIVE">{{:: 'authz-policy-logic-positive' | translate}}</option>
<option value="NEGATIVE">{{:: 'authz-policy-logic-negative' | translate}}</option> <option value="NEGATIVE">{{:: 'authz-policy-logic-negative' | translate}}</option>
</select> </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"></span>
<span ng-if="policy.details && policy.details.loaded" class="fa fa-angle-right fa-angle-down"></span> <span ng-if="policy.details && policy.details.loaded" class="fa fa-angle-right fa-angle-down"></span>
</td> </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.description}}</td>
<td>{{policy.type}}</td> <td>{{policy.type}}</td>
<td align="center"> <td align="center">

View file

@ -111,6 +111,16 @@
<artifactId>keycloak-server-spi-private</artifactId> <artifactId>keycloak-server-spi-private</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </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> <dependency>
<groupId>org.wildfly.core</groupId> <groupId>org.wildfly.core</groupId>

View file

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