Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
Expand Down Expand Up @@ -56,17 +57,17 @@ public Class<JSPolicyRepresentation> getRepresentationType() {

@Override
public void onCreate(Policy policy, JSPolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation.getCode());
updatePolicy(policy, representation.getCode(), authorization);
}

@Override
public void onUpdate(Policy policy, JSPolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation.getCode());
updatePolicy(policy, representation.getCode(), authorization);
}

@Override
public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation.getConfig().get("code"));
updatePolicy(policy, representation.getConfig().get("code"), authorization);
}

@Override
Expand Down Expand Up @@ -96,6 +97,11 @@ public String getId() {
return "js";
}

@Override
public boolean isInternal() {
return !Profile.isFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS);
}

private EvaluatableScriptAdapter getEvaluatableScript(final AuthorizationProvider authz, final Policy policy) {
return scriptCache.computeIfAbsent(policy.getId(), id -> {
final ScriptingProvider scripting = authz.getKeycloakSession().getProvider(ScriptingProvider.class);
Expand All @@ -104,7 +110,7 @@ private EvaluatableScriptAdapter getEvaluatableScript(final AuthorizationProvide
});
}

private ScriptModel getScriptModel(final Policy policy, final RealmModel realm, final ScriptingProvider scripting) {
protected ScriptModel getScriptModel(final Policy policy, final RealmModel realm, final ScriptingProvider scripting) {
String scriptName = policy.getName();
String scriptCode = policy.getConfig().get("code");
String scriptDescription = policy.getDescription();
Expand All @@ -113,8 +119,15 @@ private ScriptModel getScriptModel(final Policy policy, final RealmModel realm,
return scripting.createScript(realm.getId(), ScriptModel.TEXT_JAVASCRIPT, scriptName, scriptCode, scriptDescription);
}

private void updatePolicy(Policy policy, String code) {
private void updatePolicy(Policy policy, String code, AuthorizationProvider authorization) {
scriptCache.remove(policy.getId());
if (!Profile.isFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS) && !authorization.getKeycloakSession().getAttributeOrDefault("ALLOW_CREATE_POLICY", false) && !isDeployed()) {
throw new RuntimeException("Script upload is disabled");
}
policy.putConfig("code", code);
}

protected boolean isDeployed() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public void evaluate(Evaluation evaluation) {

if (effect == null) {
PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());

if (policyProvider == null) {
throw new RuntimeException("No policy provider found for policy [" + associatedPolicy.getType() + "]");
}

policyProvider.evaluate(defaultEvaluation);
evaluation.denyIfNoEffect();
decisions.put(permission, defaultEvaluation.getEffect());
Expand Down
23 changes: 20 additions & 3 deletions common/src/main/java/org/keycloak/common/Profile.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

package org.keycloak.common;

import static org.keycloak.common.Profile.Type.DEPRECATED;

import org.jboss.logging.Logger;

import java.io.File;
Expand All @@ -38,9 +40,9 @@ public enum Type {
DEFAULT,
DISABLED_BY_DEFAULT,
PREVIEW,
EXPERIMENTAL
EXPERIMENTAL,
DEPRECATED;
}

public enum Feature {
ACCOUNT2(Type.EXPERIMENTAL),
ACCOUNT_API(Type.PREVIEW),
Expand All @@ -50,7 +52,8 @@ public enum Feature {
OPENSHIFT_INTEGRATION(Type.PREVIEW),
SCRIPTS(Type.PREVIEW),
TOKEN_EXCHANGE(Type.PREVIEW),
AUTHZ_DROOLS_POLICY(Type.PREVIEW);
AUTHZ_DROOLS_POLICY(Type.PREVIEW),
UPLOAD_SCRIPTS(DEPRECATED);

private Type type;

Expand Down Expand Up @@ -83,6 +86,7 @@ private enum ProfileValue {
private final Set<Feature> disabledFeatures = new HashSet<>();
private final Set<Feature> previewFeatures = new HashSet<>();
private final Set<Feature> experimentalFeatures = new HashSet<>();
private final Set<Feature> deprecatedFeatures = new HashSet<>();

private Profile() {
Config config = new Config();
Expand All @@ -99,9 +103,18 @@ private Profile() {
disabledFeatures.add(f);
}
break;
case DEPRECATED:
deprecatedFeatures.add(f);
case DISABLED_BY_DEFAULT:
if (enabled == null || !enabled) {
disabledFeatures.add(f);
} else if (DEPRECATED.equals(f.getType())) {
logger.warnf("Deprecated feature enabled: " + f.name().toLowerCase());
if (Feature.UPLOAD_SCRIPTS.equals(f)) {
previewFeatures.add(Feature.SCRIPTS);
disabledFeatures.remove(Feature.SCRIPTS);
logger.warnf("Preview feature enabled: " + Feature.SCRIPTS.name().toLowerCase());
}
}
break;
case PREVIEW:
Expand Down Expand Up @@ -144,6 +157,10 @@ public static Set<Feature> getExperimentalFeatures() {
return CURRENT.experimentalFeatures;
}

public static Set<Feature> getDeprecatedFeatures() {
return CURRENT.deprecatedFeatures;
}

public static boolean isFeatureEnabled(Feature feature) {
return !CURRENT.disabledFeatures.contains(feature);
}
Expand Down
11 changes: 9 additions & 2 deletions common/src/test/java/org/keycloak/common/ProfileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import org.junit.rules.TemporaryFolder;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
Expand All @@ -22,9 +21,10 @@ public class ProfileTest {
@Test
public void checkDefaults() {
Assert.assertEquals("community", Profile.getName());
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.AUTHZ_DROOLS_POLICY, Profile.Feature.OPENSHIFT_INTEGRATION);
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.AUTHZ_DROOLS_POLICY, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS);
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.AUTHZ_DROOLS_POLICY, Profile.Feature.OPENSHIFT_INTEGRATION);
assertEquals(Profile.getExperimentalFeatures(), Profile.Feature.ACCOUNT2);
assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS);
}

@Test
Expand All @@ -33,21 +33,25 @@ public void configWithSystemProperties() {
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.DOCKER));
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION));
Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION));
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS));

System.setProperty("keycloak.profile", "preview");
System.setProperty("keycloak.profile.feature.docker", "enabled");
System.setProperty("keycloak.profile.feature.impersonation", "disabled");
System.setProperty("keycloak.profile.feature.upload_scripts", "enabled");

Profile.init();

Assert.assertEquals("preview", Profile.getName());
Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.DOCKER));
Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION));
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION));
Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS));

System.getProperties().remove("keycloak.profile");
System.getProperties().remove("keycloak.profile.feature.docker");
System.getProperties().remove("keycloak.profile.feature.impersonation");
System.getProperties().remove("keycloak.profile.feature.upload_scripts");

Profile.init();
}
Expand All @@ -57,6 +61,7 @@ public void configWithPropertiesFile() throws IOException {
Assert.assertEquals("community", Profile.getName());
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.DOCKER));
Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION));
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS));

File d = temporaryFolder.newFolder();
File f = new File(d, "profile.properties");
Expand All @@ -65,6 +70,7 @@ public void configWithPropertiesFile() throws IOException {
p.setProperty("profile", "preview");
p.setProperty("feature.docker", "enabled");
p.setProperty("feature.impersonation", "disabled");
p.setProperty("feature.upload_scripts", "enabled");
PrintWriter pw = new PrintWriter(f);
p.list(pw);
pw.close();
Expand All @@ -77,6 +83,7 @@ public void configWithPropertiesFile() throws IOException {
Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.DOCKER));
Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION));
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION));
Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.UPLOAD_SCRIPTS));

System.getProperties().remove("jboss.server.config.dir");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ public class JSPolicyRepresentation extends AbstractPolicyRepresentation {

@Override
public String getType() {
return "js";
if (super.getType() == null) {
return "js";
}
return super.getType();
}

public String getCode() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading