Merge pull request #3239 from stianst/SERVER-PROFILE

KEYCLOAK-3579 Add ability to define profiles
This commit is contained in:
Stian Thorgersen 2016-09-20 10:39:05 +02:00 committed by GitHub
commit 4977527f60
19 changed files with 357 additions and 10 deletions

View file

@ -0,0 +1,69 @@
/*
* 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.common;
import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Profile {
private enum ProfileValue {
PRODUCT, PREVIEW, COMMUNITY
}
private static ProfileValue value = load();
static ProfileValue load() {
String profile = null;
try {
profile = System.getProperty("keycloak.profile");
if (profile == null) {
String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
if (jbossServerConfigDir != null) {
File file = new File(jbossServerConfigDir, "profile.properties");
if (file.isFile()) {
Properties props = new Properties();
props.load(new FileInputStream(file));
profile = props.getProperty("profile");
}
}
}
} catch (Exception e) {
}
if (profile == null) {
return ProfileValue.valueOf(Version.DEFAULT_PROFILE.toUpperCase());
} else {
return ProfileValue.valueOf(profile.toUpperCase());
}
}
public static String getName() {
return value.name().toLowerCase();
}
public static boolean isPreviewEnabled() {
return value.ordinal() >= ProfileValue.PREVIEW.ordinal();
}
}

View file

@ -32,6 +32,7 @@ public class Version {
public static String VERSION;
public static String RESOURCES_VERSION;
public static String BUILD_TIME;
public static String DEFAULT_PROFILE;
static {
Properties props = new Properties();
@ -40,6 +41,7 @@ public class Version {
props.load(is);
Version.NAME = props.getProperty("name");
Version.NAME_HTML = props.getProperty("name-html");
Version.DEFAULT_PROFILE = props.getProperty("default-profile");
Version.VERSION = props.getProperty("version");
Version.BUILD_TIME = props.getProperty("build-time");
Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();

View file

@ -18,4 +18,5 @@
name=${product.name}
name-html=${product.name-html}
version=${product.version}
build-time=${product.build-time}
build-time=${product.build-time}
default-profile=${product.default-profile}

View file

@ -0,0 +1,53 @@
/*
* 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.representations.info;
import org.keycloak.common.Profile;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ProfileInfoRepresentation {
private String name;
private boolean previewEnabled;
public static ProfileInfoRepresentation create() {
ProfileInfoRepresentation info = new ProfileInfoRepresentation();
info.setName(Profile.getName());
info.setPreviewEnabled(Profile.isPreviewEnabled());
return info;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isPreviewEnabled() {
return previewEnabled;
}
public void setPreviewEnabled(boolean previewEnabled) {
this.previewEnabled = previewEnabled;
}
}

View file

@ -32,6 +32,7 @@ public class ServerInfoRepresentation {
private SystemInfoRepresentation systemInfo;
private MemoryInfoRepresentation memoryInfo;
private ProfileInfoRepresentation profileInfo;
private Map<String, List<ThemeInfoRepresentation>> themes;
@ -66,6 +67,14 @@ public class ServerInfoRepresentation {
this.memoryInfo = memoryInfo;
}
public ProfileInfoRepresentation getProfileInfo() {
return profileInfo;
}
public void setProfileInfo(ProfileInfoRepresentation profileInfo) {
this.profileInfo = profileInfo;
}
public Map<String, List<ThemeInfoRepresentation>> getThemes() {
return themes;
}

View file

@ -40,6 +40,7 @@
<product.name-html>\u003Cdiv class="kc-logo-text"\u003E\u003Cspan\u003EKeycloak\u003C\u002Fspan\u003E\u003C\u002Fdiv\u003E</product.name-html>
<product.version>${project.version}</product.version>
<product.build-time>${timestamp}</product.build-time>
<product.default-profile>community</product.default-profile>
<!-- WildFly -->
<eap.version>7.0.0.Beta</eap.version>

View file

@ -35,6 +35,7 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.ResolveRelative;
import org.keycloak.utils.ProfileHelper;
import org.keycloak.wellknown.WellKnownProvider;
import javax.ws.rs.GET;
@ -254,6 +255,8 @@ public class RealmsResource {
@Path("{realm}/authz")
public Object getAuthorizationService(@PathParam("realm") String name) {
ProfileHelper.requirePreview();
init(name);
AuthorizationProvider authorization = this.session.getProvider(AuthorizationProvider.class);
AuthorizationService service = new AuthorizationService(authorization);

View file

@ -18,9 +18,9 @@ package org.keycloak.services.resources.admin;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.BadRequestException;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.authorization.admin.AuthorizationService;
import org.keycloak.common.Profile;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.ClientModel;
@ -52,10 +52,12 @@ import org.keycloak.common.util.Time;
import org.keycloak.services.validation.ClientValidator;
import org.keycloak.services.validation.PairwiseClientValidator;
import org.keycloak.services.validation.ValidationMessages;
import org.keycloak.utils.ProfileHelper;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
@ -153,10 +155,12 @@ public class ClientResource {
RepresentationToModel.updateClient(rep, client);
if (TRUE.equals(rep.getAuthorizationServicesEnabled())) {
authorization().enable();
} else {
authorization().disable();
if (Profile.isPreviewEnabled()) {
if (TRUE.equals(rep.getAuthorizationServicesEnabled())) {
authorization().enable();
} else {
authorization().disable();
}
}
}
@ -177,7 +181,9 @@ public class ClientResource {
ClientRepresentation representation = ModelToRepresentation.toRepresentation(client);
representation.setAuthorizationServicesEnabled(authorization().isEnabled());
if (Profile.isPreviewEnabled()) {
representation.setAuthorizationServicesEnabled(authorization().isEnabled());
}
return representation;
}
@ -562,6 +568,8 @@ public class ClientResource {
@Path("/authz")
public AuthorizationService authorization() {
ProfileHelper.requirePreview();
AuthorizationService resource = new AuthorizationService(this.session, this.client, this.auth);
ResteasyProviderFactory.getInstance().injectProperties(resource);

View file

@ -43,6 +43,7 @@ import org.keycloak.policy.PasswordPolicyProviderFactory;
import org.keycloak.provider.*;
import org.keycloak.representations.idm.ComponentTypeRepresentation;
import org.keycloak.representations.idm.PasswordPolicyTypeRepresentation;
import org.keycloak.representations.info.ProfileInfoRepresentation;
import org.keycloak.theme.Theme;
import org.keycloak.theme.ThemeProvider;
import org.keycloak.models.KeycloakSession;
@ -84,6 +85,7 @@ public class ServerInfoAdminResource {
ServerInfoRepresentation info = new ServerInfoRepresentation();
info.setSystemInfo(SystemInfoRepresentation.create(session.getKeycloakSessionFactory().getServerStartupTimestamp()));
info.setMemoryInfo(MemoryInfoRepresentation.create());
info.setProfileInfo(ProfileInfoRepresentation.create());
setSocialProviders(info);
setIdentityProviders(info);

View file

@ -0,0 +1,36 @@
/*
* 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.utils;
import org.keycloak.common.Profile;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ProfileHelper {
public static void requirePreview() {
if (!Profile.isPreviewEnabled()) {
throw new WebApplicationException("Feature not available in current profile", Response.Status.NOT_IMPLEMENTED);
}
}
}

View file

@ -0,0 +1,36 @@
/*
* 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;
import org.junit.Assume;
import org.keycloak.common.Profile;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ProfileAssume {
public static void assumePreview() {
Assume.assumeTrue("Ignoring test as community/preview profile is not enabled", Profile.isPreviewEnabled());
}
public static void assumePreviewDisabled() {
Assume.assumeFalse("Ignoring test as community/preview profile is enabled", Profile.isPreviewEnabled());
}
}

View file

@ -19,11 +19,13 @@ package org.keycloak.testsuite.admin.client.authorization;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ResourceScopeResource;
import org.keycloak.admin.client.resource.ResourceScopesResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.client.AbstractClientTest;
import javax.ws.rs.core.Response;
@ -38,6 +40,11 @@ public abstract class AbstractAuthorizationTest extends AbstractClientTest {
protected static final String RESOURCE_SERVER_CLIENT_ID = "test-resource-server";
@BeforeClass
public static void enabled() {
ProfileAssume.assumePreview();
}
@Before
public void onBeforeAuthzTests() {
createOidcClient(RESOURCE_SERVER_CLIENT_ID);

View file

@ -0,0 +1,50 @@
/*
* 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.admin.client.authorization;
import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.client.AbstractClientTest;
import javax.ws.rs.ServerErrorException;
import javax.ws.rs.core.Response;
import static org.junit.Assert.assertEquals;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AuthorizationDisabledInPreviewTest extends AbstractClientTest {
@BeforeClass
public static void enabled() {
ProfileAssume.assumePreviewDisabled();
}
@Test
public void testAuthzServicesRemoved() {
String id = testRealmResource().clients().findAll().get(0).getId();
try {
testRealmResource().clients().get(id).authorization().getSettings();
} catch (ServerErrorException e) {
assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), e.getResponse().getStatus());
}
}
}

View file

@ -0,0 +1,65 @@
/*
* 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.console.clients;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Test;
import org.keycloak.common.Profile;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.console.page.clients.settings.ClientSettings;
import org.openqa.selenium.By;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.auth.page.login.Login.OIDC;
/**
*
* @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
*/
public class ClientAuthorizationServicesAvailableTest extends AbstractClientTest {
private ClientRepresentation newClient;
@Page
private ClientSettings clientSettingsPage;
@Test
public void authzServicesAvailable() {
ProfileAssume.assumePreview();
newClient = createClientRep("oidc-public", OIDC);
createClient(newClient);
assertEquals("oidc-public", clientSettingsPage.form().getClientId());
assertTrue(driver.findElement(By.xpath("//*[@for='authorizationServicesEnabled']")).isDisplayed());
}
@Test
public void authzServicesUnavailable() throws InterruptedException {
ProfileAssume.assumePreviewDisabled();
newClient = createClientRep("oidc-public", OIDC);
createClient(newClient);
assertEquals("oidc-public", clientSettingsPage.form().getClientId());
assertFalse(driver.findElement(By.xpath("//*[@for='authorizationServicesEnabled']")).isDisplayed());
}
}

View file

@ -851,6 +851,7 @@ include-representation=Include Representation
include-representation.tooltip=Include JSON representation for create and update requests.
clear-admin-events.tooltip=Deletes all admin events in the database.
server-version=Server Version
server-profile=Server Profile
info=Info
providers=Providers
server-time=Server Time

View file

@ -274,7 +274,7 @@ module.service('ServerInfo', function($resource, $q, $http) {
var delay = $q.defer();
$http.get(authUrl + '/admin/serverinfo').success(function(data) {
info = data;
angular.copy(data, info);
delay.resolve(info);
});

View file

@ -110,7 +110,7 @@
<input ng-model="client.serviceAccountsEnabled" name="serviceAccountsEnabled" id="serviceAccountsEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
</div>
<div class="form-group" data-ng-show="protocol == 'openid-connect'">
<div class="form-group" data-ng-show="serverInfo.profileInfo.previewEnabled && protocol == 'openid-connect'">
<label class="col-md-2 control-label" for="authorizationServicesEnabled">{{:: 'authz-authorization-services-enabled' | translate}}</label>
<kc-tooltip>{{:: 'authz-authorization-services-enabled.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">

View file

@ -14,6 +14,10 @@
<td width="20%">{{:: 'server-version' | translate}}</td>
<td>{{serverInfo.systemInfo.version}}</td>
</tr>
<tr>
<td width="20%">{{:: 'server-profile' | translate}}</td>
<td>{{serverInfo.profileInfo.name}}</td>
</tr>
<tr>
<td>{{:: 'server-time' | translate}}</td>
<td>{{serverInfo.systemInfo.serverTime}}</td>

View file

@ -19,7 +19,7 @@
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/scope-mappings">{{:: 'scope' | translate}}</a>
<kc-tooltip>{{:: 'scope.tooltip' | translate}}</kc-tooltip>
</li>
<li ng-class="{active: path[4] == 'authz'}" data-ng-show="!disableAuthorizationTab && client.authorizationServicesEnabled"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server">{{:: 'authz-authorization' | translate}}</a></li>
<li ng-class="{active: path[4] == 'authz'}" data-ng-show="serverInfo.profileInfo.previewEnabled && !disableAuthorizationTab && client.authorizationServicesEnabled"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server">{{:: 'authz-authorization' | translate}}</a></li>
<li ng-class="{active: path[4] == 'revocation'}"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/revocation">{{:: 'revocation' | translate}}</a></li>
<!-- <li ng-class="{active: path[4] == 'identity-provider'}" data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/identity-provider">Identity Provider</a></li> -->
<li ng-class="{active: path[4] == 'sessions'}" data-ng-show="!client.bearerOnly">