KEYCLOAK-12768: Prevent reserved characters in URLs
This commit is contained in:
parent
256bbff769
commit
fff8571cfd
28 changed files with 196 additions and 15 deletions
|
@ -59,6 +59,7 @@ import org.keycloak.services.clientregistration.policy.DefaultClientRegistration
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.keycloak.utils.ReservedCharValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per request object
|
* Per request object
|
||||||
|
@ -98,6 +99,7 @@ public class RealmManager {
|
||||||
|
|
||||||
public RealmModel createRealm(String id, String name) {
|
public RealmModel createRealm(String id, String name) {
|
||||||
if (id == null) id = KeycloakModelUtils.generateId();
|
if (id == null) id = KeycloakModelUtils.generateId();
|
||||||
|
ReservedCharValidator.validate(name);
|
||||||
RealmModel realm = model.createRealm(id, name);
|
RealmModel realm = model.createRealm(id, name);
|
||||||
realm.setName(name);
|
realm.setName(name);
|
||||||
|
|
||||||
|
@ -505,6 +507,7 @@ public class RealmManager {
|
||||||
id = KeycloakModelUtils.generateId();
|
id = KeycloakModelUtils.generateId();
|
||||||
}
|
}
|
||||||
RealmModel realm = model.createRealm(id, rep.getRealm());
|
RealmModel realm = model.createRealm(id, rep.getRealm());
|
||||||
|
ReservedCharValidator.validate(rep.getRealm());
|
||||||
realm.setName(rep.getRealm());
|
realm.setName(rep.getRealm());
|
||||||
|
|
||||||
// setup defaults
|
// setup defaults
|
||||||
|
|
|
@ -72,6 +72,7 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
|
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
|
||||||
|
import org.keycloak.utils.ReservedCharValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @resource Authentication Management
|
* @resource Authentication Management
|
||||||
|
@ -214,6 +215,8 @@ public class AuthenticationManagementResource {
|
||||||
if (realm.getFlowByAlias(flow.getAlias()) != null) {
|
if (realm.getFlowByAlias(flow.getAlias()) != null) {
|
||||||
return ErrorResponse.exists("Flow " + flow.getAlias() + " already exists");
|
return ErrorResponse.exists("Flow " + flow.getAlias() + " already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReservedCharValidator.validate(flow.getAlias());
|
||||||
|
|
||||||
AuthenticationFlowModel createdModel = realm.addAuthenticationFlow(RepresentationToModel.toModel(flow));
|
AuthenticationFlowModel createdModel = realm.addAuthenticationFlow(RepresentationToModel.toModel(flow));
|
||||||
|
|
||||||
|
@ -788,6 +791,8 @@ public class AuthenticationManagementResource {
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public Response newExecutionConfig(@PathParam("executionId") String execution, AuthenticatorConfigRepresentation json) {
|
public Response newExecutionConfig(@PathParam("executionId") String execution, AuthenticatorConfigRepresentation json) {
|
||||||
auth.realm().requireManageRealm();
|
auth.realm().requireManageRealm();
|
||||||
|
|
||||||
|
ReservedCharValidator.validate(json.getAlias());
|
||||||
|
|
||||||
AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution);
|
AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution);
|
||||||
if (model == null) {
|
if (model == null) {
|
||||||
|
@ -1137,6 +1142,8 @@ public class AuthenticationManagementResource {
|
||||||
public Response createAuthenticatorConfig(AuthenticatorConfigRepresentation rep) {
|
public Response createAuthenticatorConfig(AuthenticatorConfigRepresentation rep) {
|
||||||
auth.realm().requireManageRealm();
|
auth.realm().requireManageRealm();
|
||||||
|
|
||||||
|
ReservedCharValidator.validate(rep.getAlias());
|
||||||
|
|
||||||
AuthenticatorConfigModel config = realm.addAuthenticatorConfig(RepresentationToModel.toModel(rep));
|
AuthenticatorConfigModel config = realm.addAuthenticatorConfig(RepresentationToModel.toModel(rep));
|
||||||
adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTHENTICATOR_CONFIG).resourcePath(session.getContext().getUri(), config.getId()).representation(rep).success();
|
adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTHENTICATOR_CONFIG).resourcePath(session.getContext().getUri(), config.getId()).representation(rep).success();
|
||||||
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(config.getId()).build()).build();
|
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(config.getId()).build()).build();
|
||||||
|
@ -1202,11 +1209,12 @@ public class AuthenticationManagementResource {
|
||||||
public void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigRepresentation rep) {
|
public void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigRepresentation rep) {
|
||||||
auth.realm().requireManageRealm();
|
auth.realm().requireManageRealm();
|
||||||
|
|
||||||
|
ReservedCharValidator.validate(rep.getAlias());
|
||||||
AuthenticatorConfigModel exists = realm.getAuthenticatorConfigById(id);
|
AuthenticatorConfigModel exists = realm.getAuthenticatorConfigById(id);
|
||||||
if (exists == null) {
|
if (exists == null) {
|
||||||
throw new NotFoundException("Could not find authenticator config");
|
throw new NotFoundException("Could not find authenticator config");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exists.setAlias(rep.getAlias());
|
exists.setAlias(rep.getAlias());
|
||||||
exists.setConfig(RepresentationToModel.removeEmptyString(rep.getConfig()));
|
exists.setConfig(RepresentationToModel.removeEmptyString(rep.getConfig()));
|
||||||
realm.updateAuthenticatorConfig(exists);
|
realm.updateAuthenticatorConfig(exists);
|
||||||
|
|
|
@ -87,6 +87,7 @@ import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import static java.lang.Boolean.TRUE;
|
import static java.lang.Boolean.TRUE;
|
||||||
|
import org.keycloak.utils.ReservedCharValidator;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -565,6 +566,9 @@ public class ClientResource {
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
throw new BadRequestException("Node not found in params");
|
throw new BadRequestException("Node not found in params");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReservedCharValidator.validate(node);
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) logger.debug("Register node: " + node);
|
if (logger.isDebugEnabled()) logger.debug("Register node: " + node);
|
||||||
client.registerNode(node, Time.currentTime());
|
client.registerNode(node, Time.currentTime());
|
||||||
adminEvent.operation(OperationType.CREATE).resource(ResourceType.CLUSTER_NODE).resourcePath(session.getContext().getUri(), node).success();
|
adminEvent.operation(OperationType.CREATE).resource(ResourceType.CLUSTER_NODE).resourcePath(session.getContext().getUri(), node).success();
|
||||||
|
|
|
@ -55,6 +55,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
||||||
|
import org.keycloak.utils.ReservedCharValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @resource Identity Providers
|
* @resource Identity Providers
|
||||||
|
@ -134,6 +135,9 @@ public class IdentityProvidersResource {
|
||||||
if (!(data.containsKey("providerId") && data.containsKey("fromUrl"))) {
|
if (!(data.containsKey("providerId") && data.containsKey("fromUrl"))) {
|
||||||
throw new BadRequestException();
|
throw new BadRequestException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReservedCharValidator.validate((String)data.get("alias"));
|
||||||
|
|
||||||
String providerId = data.get("providerId").toString();
|
String providerId = data.get("providerId").toString();
|
||||||
String from = data.get("fromUrl").toString();
|
String from = data.get("fromUrl").toString();
|
||||||
InputStream inputStream = session.getProvider(HttpClientProvider.class).get(from);
|
InputStream inputStream = session.getProvider(HttpClientProvider.class).get(from);
|
||||||
|
@ -182,6 +186,8 @@ public class IdentityProvidersResource {
|
||||||
public Response create(IdentityProviderRepresentation representation) {
|
public Response create(IdentityProviderRepresentation representation) {
|
||||||
this.auth.realm().requireManageIdentityProviders();
|
this.auth.realm().requireManageIdentityProviders();
|
||||||
|
|
||||||
|
ReservedCharValidator.validate(representation.getAlias());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
IdentityProviderModel identityProvider = RepresentationToModel.toModel(realm, representation, session);
|
IdentityProviderModel identityProvider = RepresentationToModel.toModel(realm, representation, session);
|
||||||
this.realm.addIdentityProvider(identityProvider);
|
this.realm.addIdentityProvider(identityProvider);
|
||||||
|
|
|
@ -106,6 +106,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.keycloak.models.utils.StripSecretsUtils.stripForExport;
|
import static org.keycloak.models.utils.StripSecretsUtils.stripForExport;
|
||||||
import static org.keycloak.util.JsonSerialization.readValue;
|
import static org.keycloak.util.JsonSerialization.readValue;
|
||||||
|
import org.keycloak.utils.ReservedCharValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base resource class for the admin REST api of one realm
|
* Base resource class for the admin REST api of one realm
|
||||||
|
@ -389,6 +390,8 @@ public class RealmAdminResource {
|
||||||
if (Config.getAdminRealm().equals(realm.getName()) && (rep.getRealm() != null && !rep.getRealm().equals(Config.getAdminRealm()))) {
|
if (Config.getAdminRealm().equals(realm.getName()) && (rep.getRealm() != null && !rep.getRealm().equals(Config.getAdminRealm()))) {
|
||||||
return ErrorResponse.error("Can't rename master realm", Status.BAD_REQUEST);
|
return ErrorResponse.error("Can't rename master realm", Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReservedCharValidator.validate(rep.getRealm());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!Constants.GENERATE.equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) {
|
if (!Constants.GENERATE.equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) {
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package org.keycloak.services.resources.admin;
|
package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import javax.ws.rs.NotFoundException;
|
import javax.ws.rs.NotFoundException;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
@ -61,6 +60,7 @@ import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import org.keycloak.utils.ReservedCharValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @resource Roles
|
* @resource Roles
|
||||||
|
@ -136,6 +136,8 @@ public class RoleContainerResource extends RoleResource {
|
||||||
if (rep.getName() == null) {
|
if (rep.getName() == null) {
|
||||||
throw new BadRequestException();
|
throw new BadRequestException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReservedCharValidator.validate(rep.getName());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
RoleModel role = roleContainer.addRole(rep.getName());
|
RoleModel role = roleContainer.addRole(rep.getName());
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 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 javax.ws.rs.BadRequestException;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert
|
||||||
|
*/
|
||||||
|
public class ReservedCharValidator {
|
||||||
|
protected static final Logger logger = Logger.getLogger(ReservedCharValidator.class);
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc3986#section-2.2
|
||||||
|
private static final String[] RESERVED_CHARS = { ":", "/", "?", "#", "[", "@", "!", "$",
|
||||||
|
"&", "(", ")", "*", "+", ",", ";", "=",
|
||||||
|
"]", "[", "\\" };
|
||||||
|
private ReservedCharValidator() {}
|
||||||
|
|
||||||
|
public static void validate(String str) throws ReservedCharException {
|
||||||
|
if (str == null) return;
|
||||||
|
|
||||||
|
for (String c : RESERVED_CHARS) {
|
||||||
|
if (str.contains(c)) {
|
||||||
|
String message = "Character '" + c + "' not allowed.";
|
||||||
|
ReservedCharException e = new ReservedCharException(message);
|
||||||
|
logger.warn(message, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ReservedCharException extends BadRequestException {
|
||||||
|
ReservedCharException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,7 +40,7 @@ public class PatternFlyClosableAlert extends AbstractPatternFlyAlert {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWarning() {
|
public boolean isWarning() {
|
||||||
return checkAlertType("waring");
|
return checkAlertType("warning");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDanger() {
|
public boolean isDanger() {
|
||||||
|
|
|
@ -422,6 +422,16 @@ public class ClientTest extends AbstractAdminTest {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test (expected = BadRequestException.class)
|
||||||
|
public void testAddNodeWithReservedCharacter() {
|
||||||
|
testingClient.testApp().clearAdminActions();
|
||||||
|
|
||||||
|
ClientRepresentation client = createAppClient();
|
||||||
|
String id = client.getId();
|
||||||
|
|
||||||
|
realm.clients().get(id).registerNode(Collections.singletonMap("node", "foo#"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void nodes() {
|
public void nodes() {
|
||||||
testingClient.testApp().clearAdminActions();
|
testingClient.testApp().clearAdminActions();
|
||||||
|
|
|
@ -123,6 +123,17 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
Assert.assertNames(realm.identityProviders().findAll(), "google", "facebook");
|
Assert.assertNames(realm.identityProviders().findAll(), "google", "facebook");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateWithReservedCharacterForAlias() {
|
||||||
|
IdentityProviderRepresentation newIdentityProvider = createRep("ne$&w-identity-provider", "oidc");
|
||||||
|
|
||||||
|
newIdentityProvider.getConfig().put("clientId", "clientId");
|
||||||
|
newIdentityProvider.getConfig().put("clientSecret", "some secret value");
|
||||||
|
|
||||||
|
Response response = realm.identityProviders().create(newIdentityProvider);
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreate() {
|
public void testCreate() {
|
||||||
IdentityProviderRepresentation newIdentityProvider = createRep("new-identity-provider", "oidc");
|
IdentityProviderRepresentation newIdentityProvider = createRep("new-identity-provider", "oidc");
|
||||||
|
|
|
@ -34,6 +34,7 @@ import javax.ws.rs.core.Response;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import javax.ws.rs.BadRequestException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -58,6 +59,12 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest {
|
||||||
executionId = exec.getId();
|
executionId = exec.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateConfigWithReservedChar() {
|
||||||
|
AuthenticatorConfigRepresentation cfg = newConfig("f!oo", IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "true");
|
||||||
|
Response resp = authMgmtResource.newExecutionConfig(executionId, cfg);
|
||||||
|
Assert.assertEquals(400, resp.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateConfig() {
|
public void testCreateConfig() {
|
||||||
|
@ -80,7 +87,16 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest {
|
||||||
assertAdminEvents.assertEvent(REALM_NAME, OperationType.DELETE, AdminEventPaths.authExecutionConfigPath(cfgId), ResourceType.AUTHENTICATOR_CONFIG);
|
assertAdminEvents.assertEvent(REALM_NAME, OperationType.DELETE, AdminEventPaths.authExecutionConfigPath(cfgId), ResourceType.AUTHENTICATOR_CONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test (expected = BadRequestException.class)
|
||||||
|
public void testUpdateConfigWithBadChar() {
|
||||||
|
AuthenticatorConfigRepresentation cfg = newConfig("foo", IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "true");
|
||||||
|
String cfgId = createConfig(executionId, cfg);
|
||||||
|
AuthenticatorConfigRepresentation cfgRep = authMgmtResource.getAuthenticatorConfig(cfgId);
|
||||||
|
|
||||||
|
cfgRep.setAlias("Bad@Char");
|
||||||
|
authMgmtResource.updateAuthenticatorConfig(cfgRep.getId(), cfgRep);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateConfig() {
|
public void testUpdateConfig() {
|
||||||
AuthenticatorConfigRepresentation cfg = newConfig("foo", IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "true");
|
AuthenticatorConfigRepresentation cfg = newConfig("foo", IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "true");
|
||||||
|
|
|
@ -72,6 +72,12 @@ public class FlowTest extends AbstractAuthenticationTest {
|
||||||
authMgmtResource.addExecutionFlow(parentAlias, data);
|
authMgmtResource.addExecutionFlow(parentAlias, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddFlowWithRestrictedCharInAlias() {
|
||||||
|
Response resp = authMgmtResource.createFlow(newFlow("fo]o", "Browser flow", "basic-flow", true, false));
|
||||||
|
Assert.assertEquals(400, resp.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddRemoveFlow() {
|
public void testAddRemoveFlow() {
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import javax.ws.rs.BadRequestException;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
@ -87,6 +88,12 @@ public class ClientRolesTest extends AbstractClientTest {
|
||||||
assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientRoleResourcePath(clientDbId, "role1"), role1, ResourceType.CLIENT_ROLE);
|
assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientRoleResourcePath(clientDbId, "role1"), role1, ResourceType.CLIENT_ROLE);
|
||||||
assertTrue(hasRole(rolesRsc, "role1"));
|
assertTrue(hasRole(rolesRsc, "role1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = BadRequestException.class)
|
||||||
|
public void testAddRoleWithReservedCharacter() {
|
||||||
|
RoleRepresentation role1 = makeRole("r&ole1");
|
||||||
|
rolesRsc.create(role1);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemoveRole() {
|
public void testRemoveRole() {
|
||||||
|
|
|
@ -157,6 +157,13 @@ public class RealmTest extends AbstractAdminTest {
|
||||||
|
|
||||||
Assert.assertNames(adminClient.realms().findAll(), "master", AuthRealm.TEST, REALM_NAME);
|
Assert.assertNames(adminClient.realms().findAll(), "master", AuthRealm.TEST, REALM_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = BadRequestException.class)
|
||||||
|
public void createRealmRejectReservedChar() {
|
||||||
|
RealmRepresentation rep = new RealmRepresentation();
|
||||||
|
rep.setRealm("new-re;alm");
|
||||||
|
adminClient.realms().create(rep);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks attributes exposed as fields are not also included as attributes
|
* Checks attributes exposed as fields are not also included as attributes
|
||||||
|
@ -379,6 +386,13 @@ public class RealmTest extends AbstractAdminTest {
|
||||||
checkRealmEventsConfigRepresentation(repOrig, actual);
|
checkRealmEventsConfigRepresentation(repOrig, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = BadRequestException.class)
|
||||||
|
public void updateRealmWithReservedCharInName() {
|
||||||
|
RealmRepresentation rep = realm.toRepresentation();
|
||||||
|
rep.setRealm("fo#o");
|
||||||
|
realm.update(rep);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateRealm() {
|
public void updateRealm() {
|
||||||
// first change
|
// first change
|
||||||
|
|
|
@ -137,6 +137,10 @@ public abstract class AbstractConsoleTest extends AbstractAuthTest {
|
||||||
public void assertAlertDanger() {
|
public void assertAlertDanger() {
|
||||||
alert.assertDanger();
|
alert.assertDanger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void assertAlertWarning() {
|
||||||
|
alert.assertWarning();
|
||||||
|
}
|
||||||
|
|
||||||
public ConfigureMenu configure() {
|
public ConfigureMenu configure() {
|
||||||
return adminConsoleRealmPage.configure();
|
return adminConsoleRealmPage.configure();
|
||||||
|
|
|
@ -112,6 +112,21 @@ public class RealmRolesTest extends AbstractRolesTest {
|
||||||
assertNotNull(realmRolesPage.table().findRole(name));
|
assertNotNull(realmRolesPage.table().findRole(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KEYCLOAK-12768: Certain characters in names cause bad URIs. Disallow.
|
||||||
|
@Test
|
||||||
|
public void testAddRoleWithBadCharsInName() {
|
||||||
|
String roleName = "hello;:]!@#role";
|
||||||
|
assertCurrentUrlEquals(realmRolesPage);
|
||||||
|
realmRolesPage.table().addRole();
|
||||||
|
assertCurrentUrlEquals(createRolePage);
|
||||||
|
createRolePage.form().setName(roleName);
|
||||||
|
assertAlertWarning();
|
||||||
|
createRolePage.form().save();
|
||||||
|
assertAlertSuccess();
|
||||||
|
realmRolesPage.navigateTo();
|
||||||
|
assertNotNull(realmRolesPage.table().findRole("hellorole"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddExistingRole() {
|
public void testAddExistingRole() {
|
||||||
addRole(testRole);
|
addRole(testRole);
|
||||||
|
|
|
@ -1608,3 +1608,5 @@ subjectdn-tooltip=A regular expression for validating Subject DN in the Client C
|
||||||
|
|
||||||
pkce-code-challenge-method=Proof Key for Code Exchange Code Challenge Method
|
pkce-code-challenge-method=Proof Key for Code Exchange Code Challenge Method
|
||||||
pkce-code-challenge-method.tooltip=Choose which code challenge method for PKCE is used. If not specified, keycloak does not applies PKCE to a client unless the client sends an authorization request with appropriate code challenge and code exchange method.
|
pkce-code-challenge-method.tooltip=Choose which code challenge method for PKCE is used. If not specified, keycloak does not applies PKCE to a client unless the client sends an authorization request with appropriate code challenge and code exchange method.
|
||||||
|
|
||||||
|
key-not-allowed-here=Key '{{character}}' is not allowed here.
|
||||||
|
|
|
@ -2384,6 +2384,23 @@ module.directive('kcEnter', function() {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Don't allow URI reserved characters
|
||||||
|
module.directive('kcNoReservedChars', function (Notifications, $translate) {
|
||||||
|
return function($scope, element) {
|
||||||
|
element.bind("keydown keypress", function(event) {
|
||||||
|
var keyPressed = String.fromCharCode(event.which || event.keyCode || 0);
|
||||||
|
|
||||||
|
// ] and ' can not be used inside a character set on POSIX and GNU
|
||||||
|
if (keyPressed.match('[:/?#[@!$&()*+,;=]') || keyPressed === ']' || keyPressed === '\'') {
|
||||||
|
event.preventDefault();
|
||||||
|
$scope.$apply(function() {
|
||||||
|
Notifications.warn($translate.instant('key-not-allowed-here', {character: keyPressed}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
module.directive('kcSave', function ($compile, $timeout, Notifications) {
|
module.directive('kcSave', function ($compile, $timeout, Notifications) {
|
||||||
var clickDelay = 500; // 500 ms
|
var clickDelay = 500; // 500 ms
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-md-2 control-label" for="name">{{:: 'alias' | translate}}</label>
|
<label class="col-md-2 control-label" for="name">{{:: 'alias' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" id="name" type="text" ng-model="config.alias" data-ng-readonly="!create">
|
<input kc-no-reserved-chars class="form-control" id="name" type="text" ng-model="config.alias" data-ng-readonly="!create">
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>{{:: 'authenticator.alias.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'authenticator.alias.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="host">{{:: 'host' | translate}}</label>
|
<label class="col-md-2 control-label" for="host">{{:: 'host' | translate}}</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<input ng-disabled="!create" class="form-control" type="text" id="host" name="host" data-ng-model="node.host" required>
|
<input kc-no-reserved-chars ng-disabled="!create" class="form-control" type="text" id="host" name="host" data-ng-model="node.host" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-hide="create" class="form-group">
|
<div ng-hide="create" class="form-group">
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<label class="col-md-2 control-label" for="name">{{:: 'role-name' | translate}} <span class="required" data-ng-show="create">*</span></label>
|
<label class="col-md-2 control-label" for="name">{{:: 'role-name' | translate}} <span class="required" data-ng-show="create">*</span></label>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" type="text" id="name" name="name" data-ng-model="role.name" autofocus
|
<input kc-no-reserved-chars class="form-control" type="text" id="name" name="name" data-ng-model="role.name" autofocus
|
||||||
required data-ng-readonly="!create">
|
required data-ng-readonly="!create">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="alias">{{:: 'alias' | translate}} </label>
|
<label class="col-md-2 control-label" for="alias">{{:: 'alias' | translate}} </label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<input class="form-control" type="text" id="alias" name="alias" data-ng-model="flow.alias" autofocus required>
|
<input kc-no-reserved-chars class="form-control" type="text" id="alias" name="alias" data-ng-model="flow.alias" autofocus required>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>{{:: 'flow.alias.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'flow.alias.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<label for="name" class="col-sm-2 control-label">{{:: 'name' | translate}} <span class="required">*</span></label>
|
<label for="name" class="col-sm-2 control-label">{{:: 'name' | translate}} <span class="required">*</span></label>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" type="text" id="name" name="name" data-ng-model="realm.realm" autofocusrequired>
|
<input class="form-control" kc-no-reserved-chars type="text" id="name" name="name" data-ng-model="realm.realm" autofocusrequired>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="name"><span class="required">*</span> {{:: 'name' | translate}}</label>
|
<label class="col-md-2 control-label" for="name"><span class="required">*</span> {{:: 'name' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" data-ng-disabled="disableRename" type="text" id="name" name="name" data-ng-model="realmName" autofocus required>
|
<input kc-no-reserved-chars class="form-control" data-ng-disabled="disableRename" type="text" id="name" name="name" data-ng-model="realmName" autofocus required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-md-2 control-label" for="identifier"><span class="required">*</span> {{:: 'alias' | translate}}</label>
|
<label class="col-md-2 control-label" for="identifier"><span class="required">*</span> {{:: 'alias' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" id="identifier" type="text" ng-model="identityProvider.alias" data-ng-readonly="!newIdentityProvider" required>
|
<input kc-no-reserved-chars class="form-control" id="identifier" type="text" ng-model="identityProvider.alias" data-ng-readonly="!newIdentityProvider" required>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>{{:: 'identity-provider.alias.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'identity-provider.alias.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-md-2 control-label" for="identifier"><span class="required">*</span> {{:: 'alias' | translate}}</label>
|
<label class="col-md-2 control-label" for="identifier"><span class="required">*</span> {{:: 'alias' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" id="identifier" type="text" ng-model="identityProvider.alias" data-ng-readonly="!newIdentityProvider" required>
|
<input kc-no-reserved-chars class="form-control" id="identifier" type="text" ng-model="identityProvider.alias" data-ng-readonly="!newIdentityProvider" required>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>{{:: 'identity-provider.alias.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'identity-provider.alias.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<label class="col-md-2 control-label" for="name"><span class="required" data-ng-show="create">*</span> {{:: 'role-name' | translate}}</label>
|
<label class="col-md-2 control-label" for="name"><span class="required" data-ng-show="create">*</span> {{:: 'role-name' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" type="text" id="name" name="name" data-ng-model="role.name" autofocus
|
<input kc-no-reserved-chars class="form-control" type="text" id="name" name="name" data-ng-model="role.name" autofocus
|
||||||
required data-ng-readonly="!create">
|
required data-ng-readonly="!create">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<label class="col-md-2 control-label">{{:: option.label | translate}}</label>
|
<label class="col-md-2 control-label">{{:: option.label | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-6" data-ng-if="option.type == 'String'">
|
<div class="col-md-6" data-ng-if="option.type == 'String'">
|
||||||
<input class="form-control" type="text" data-ng-model="config[ option.name ]" >
|
<input kc-no-reserved-chars class="form-control" type="text" data-ng-model="config[ option.name ]" >
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6" data-ng-if="option.type == 'Password'">
|
<div class="col-md-6" data-ng-if="option.type == 'Password'">
|
||||||
<input class="form-control" type="password" data-ng-model="config[ option.name ]" >
|
<input class="form-control" type="password" data-ng-model="config[ option.name ]" >
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
<div class="col-md-6" data-ng-if="option.type == 'Role'">
|
<div class="col-md-6" data-ng-if="option.type == 'Role'">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<input class="form-control" type="text" data-ng-model="config[ option.name ]" >
|
<input kc-no-reserved-chars class="form-control" type="text" data-ng-model="config[ option.name ]" >
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<button type="button" data-ng-click="openRoleSelector(option.name, config)" class="btn btn-default" tooltip-placement="top" tooltip-trigger="mouseover mouseout" tooltip="{{:: 'selectRole.tooltip' | translate}}">{{:: 'selectRole.label' | translate}}</button>
|
<button type="button" data-ng-click="openRoleSelector(option.name, config)" class="btn btn-default" tooltip-placement="top" tooltip-trigger="mouseover mouseout" tooltip="{{:: 'selectRole.tooltip' | translate}}">{{:: 'selectRole.label' | translate}}</button>
|
||||||
|
|
Loading…
Reference in a new issue