KEYCLOAK-13128 Security Headers SPI and response filter
This commit is contained in:
parent
b40c12c712
commit
5b017e930d
42 changed files with 680 additions and 159 deletions
|
@ -137,7 +137,7 @@ public class RealmEntity {
|
||||||
@Column(name="EMAIL_THEME")
|
@Column(name="EMAIL_THEME")
|
||||||
protected String emailTheme;
|
protected String emailTheme;
|
||||||
|
|
||||||
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm", fetch = FetchType.EAGER)
|
||||||
Collection<RealmAttributeEntity> attributes = new ArrayList<>();
|
Collection<RealmAttributeEntity> attributes = new ArrayList<>();
|
||||||
|
|
||||||
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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.headers;
|
||||||
|
|
||||||
|
public interface SecurityHeadersOptions {
|
||||||
|
|
||||||
|
SecurityHeadersOptions allowFrameSrc(String source);
|
||||||
|
|
||||||
|
SecurityHeadersOptions allowAnyFrameAncestor();
|
||||||
|
|
||||||
|
SecurityHeadersOptions skipHeaders();
|
||||||
|
|
||||||
|
SecurityHeadersOptions allowEmptyContentType();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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.headers;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
import javax.ws.rs.container.ContainerRequestContext;
|
||||||
|
import javax.ws.rs.container.ContainerResponseContext;
|
||||||
|
|
||||||
|
public interface SecurityHeadersProvider extends Provider {
|
||||||
|
|
||||||
|
SecurityHeadersOptions options();
|
||||||
|
|
||||||
|
void addHeaders(ContainerRequestContext requestContext, ContainerResponseContext responseContext);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.headers;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
public interface SecurityHeadersProviderFactory extends ProviderFactory<SecurityHeadersProvider> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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.headers;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.provider.Spi;
|
||||||
|
|
||||||
|
public class SecurityHeadersSpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "security-headers";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Provider> getProviderClass() {
|
||||||
|
return SecurityHeadersProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
|
return SecurityHeadersProviderFactory.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ public class BrowserSecurityHeaders {
|
||||||
|
|
||||||
public static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy";
|
public static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy";
|
||||||
|
|
||||||
public static final String CONTENT_SECURITY_POLICY_DEFAULT = "frame-src 'self'; frame-ancestors 'self'; object-src 'none';";
|
public static final String CONTENT_SECURITY_POLICY_DEFAULT = ContentSecurityPolicyBuilder.create().build();
|
||||||
|
|
||||||
public static final String CONTENT_SECURITY_POLICY_KEY = "contentSecurityPolicy";
|
public static final String CONTENT_SECURITY_POLICY_KEY = "contentSecurityPolicy";
|
||||||
|
|
||||||
|
@ -94,4 +94,52 @@ public class BrowserSecurityHeaders {
|
||||||
defaultHeaders = Collections.unmodifiableMap(dh);
|
defaultHeaders = Collections.unmodifiableMap(dh);
|
||||||
headerAttributeMap = Collections.unmodifiableMap(headerMap);
|
headerAttributeMap = Collections.unmodifiableMap(headerMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ContentSecurityPolicyBuilder {
|
||||||
|
|
||||||
|
private String frameSrc = "self";
|
||||||
|
private String frameAncestors = "self";
|
||||||
|
private String objectSrc = "none";
|
||||||
|
|
||||||
|
private boolean first;
|
||||||
|
private StringBuilder sb;
|
||||||
|
|
||||||
|
public static ContentSecurityPolicyBuilder create() {
|
||||||
|
return new ContentSecurityPolicyBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentSecurityPolicyBuilder frameSrc(String frameSrc) {
|
||||||
|
this.frameSrc = frameSrc;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentSecurityPolicyBuilder frameAncestors(String frameancestors) {
|
||||||
|
this.frameAncestors = frameancestors;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String build() {
|
||||||
|
sb = new StringBuilder();
|
||||||
|
first = true;
|
||||||
|
|
||||||
|
build("frame-src", frameSrc);
|
||||||
|
build("frame-ancestors", frameAncestors);
|
||||||
|
build("object-src", objectSrc);
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void build(String k, String v) {
|
||||||
|
if (v != null) {
|
||||||
|
if (!first) {
|
||||||
|
sb.append(" ");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
sb.append(k).append(" '").append(v).append("';");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,3 +79,4 @@ org.keycloak.vault.VaultSpi
|
||||||
org.keycloak.crypto.CekManagementSpi
|
org.keycloak.crypto.CekManagementSpi
|
||||||
org.keycloak.crypto.ContentEncryptionSpi
|
org.keycloak.crypto.ContentEncryptionSpi
|
||||||
org.keycloak.validation.ClientValidationSPI
|
org.keycloak.validation.ClientValidationSPI
|
||||||
|
org.keycloak.headers.SecurityHeadersSpi
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class BrowserSecurityHeadersTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void contentSecurityPolicyBuilderTest() {
|
||||||
|
assertEquals("frame-src 'self'; frame-ancestors 'self'; object-src 'none';", BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create().build());
|
||||||
|
assertEquals("frame-ancestors 'self'; object-src 'none';", BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create().frameSrc(null).build());
|
||||||
|
assertEquals("frame-src 'self'; object-src 'none';", BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create().frameAncestors(null).build());
|
||||||
|
assertEquals("frame-src 'custom-frame-src'; frame-ancestors 'custom-frame-ancestors'; object-src 'none';", BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create().frameSrc("custom-frame-src").frameAncestors("custom-frame-ancestors").build());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
|
@ -160,7 +161,7 @@ public class PolicyService {
|
||||||
Policy model = storeFactory.getPolicyStore().findByName(name, this.resourceServer.getId());
|
Policy model = storeFactory.getPolicyStore().findByName(name, this.resourceServer.getId());
|
||||||
|
|
||||||
if (model == null) {
|
if (model == null) {
|
||||||
return Response.status(Status.OK).build();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.ok(toRepresentation(model, fields, authorization)).build();
|
return Response.ok(toRepresentation(model, fields, authorization)).build();
|
||||||
|
@ -219,7 +220,7 @@ public class PolicyService {
|
||||||
Set<String> resources = resourceStore.findByResourceServer(resourceFilters, resourceServer.getId(), -1, 1).stream().map(Resource::getId).collect(Collectors.toSet());
|
Set<String> resources = resourceStore.findByResourceServer(resourceFilters, resourceServer.getId(), -1, 1).stream().map(Resource::getId).collect(Collectors.toSet());
|
||||||
|
|
||||||
if (resources.isEmpty()) {
|
if (resources.isEmpty()) {
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
search.put("resource", resources.toArray(new String[resources.size()]));
|
search.put("resource", resources.toArray(new String[resources.size()]));
|
||||||
|
@ -240,7 +241,7 @@ public class PolicyService {
|
||||||
Set<String> scopes = scopeStore.findByResourceServer(scopeFilters, resourceServer.getId(), -1, 1).stream().map(Scope::getId).collect(Collectors.toSet());
|
Set<String> scopes = scopeStore.findByResourceServer(scopeFilters, resourceServer.getId(), -1, 1).stream().map(Scope::getId).collect(Collectors.toSet());
|
||||||
|
|
||||||
if (scopes.isEmpty()) {
|
if (scopes.isEmpty()) {
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
search.put("scope", scopes.toArray(new String[scopes.size()]));
|
search.put("scope", scopes.toArray(new String[scopes.size()]));
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.headers.SecurityHeadersProvider;
|
||||||
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.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -40,6 +41,7 @@ import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -109,6 +111,9 @@ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Empty content with ok makes no sense. Should it display a page? Or use noContent?
|
||||||
|
session.getProvider(SecurityHeadersProvider.class).options().allowEmptyContentType();
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,6 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.services.util.CacheControlUtil;
|
import org.keycloak.services.util.CacheControlUtil;
|
||||||
import org.keycloak.theme.BrowserSecurityHeaderSetup;
|
|
||||||
import org.keycloak.theme.FreeMarkerException;
|
import org.keycloak.theme.FreeMarkerException;
|
||||||
import org.keycloak.theme.FreeMarkerUtil;
|
import org.keycloak.theme.FreeMarkerUtil;
|
||||||
import org.keycloak.theme.Theme;
|
import org.keycloak.theme.Theme;
|
||||||
|
@ -50,7 +49,6 @@ import org.keycloak.theme.beans.MessageType;
|
||||||
import org.keycloak.theme.beans.MessagesPerFieldBean;
|
import org.keycloak.theme.beans.MessagesPerFieldBean;
|
||||||
import org.keycloak.utils.MediaType;
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
import javax.ws.rs.core.CacheControl;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -271,7 +269,6 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
||||||
try {
|
try {
|
||||||
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
|
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
|
||||||
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8_TYPE).language(locale).entity(result);
|
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8_TYPE).language(locale).entity(result);
|
||||||
BrowserSecurityHeaderSetup.headers(builder, realm);
|
|
||||||
builder.cacheControl(CacheControlUtil.noCache());
|
builder.cacheControl(CacheControlUtil.noCache());
|
||||||
return builder.build();
|
return builder.build();
|
||||||
} catch (FreeMarkerException e) {
|
} catch (FreeMarkerException e) {
|
||||||
|
|
|
@ -53,7 +53,6 @@ import org.keycloak.services.Urls;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.LoginActionsService;
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
import org.keycloak.theme.BrowserSecurityHeaderSetup;
|
|
||||||
import org.keycloak.theme.FreeMarkerException;
|
import org.keycloak.theme.FreeMarkerException;
|
||||||
import org.keycloak.theme.FreeMarkerUtil;
|
import org.keycloak.theme.FreeMarkerUtil;
|
||||||
import org.keycloak.theme.Theme;
|
import org.keycloak.theme.Theme;
|
||||||
|
@ -462,7 +461,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
String result = freeMarker.processTemplate(attributes, templateName, theme);
|
String result = freeMarker.processTemplate(attributes, templateName, theme);
|
||||||
javax.ws.rs.core.MediaType mediaType = contentType == null ? MediaType.TEXT_HTML_UTF_8_TYPE : contentType;
|
javax.ws.rs.core.MediaType mediaType = contentType == null ? MediaType.TEXT_HTML_UTF_8_TYPE : contentType;
|
||||||
Response.ResponseBuilder builder = Response.status(status == null ? Response.Status.OK : status).type(mediaType).language(locale).entity(result);
|
Response.ResponseBuilder builder = Response.status(status == null ? Response.Status.OK : status).type(mediaType).language(locale).entity(result);
|
||||||
BrowserSecurityHeaderSetup.headers(builder, realm);
|
|
||||||
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
||||||
builder.header(entry.getKey(), entry.getValue());
|
builder.header(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* 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.headers;
|
||||||
|
|
||||||
|
public class DefaultSecurityHeadersOptions implements SecurityHeadersOptions {
|
||||||
|
|
||||||
|
private boolean skipHeaders;
|
||||||
|
private boolean allowAnyFrameAncestor;
|
||||||
|
private boolean allowEmptyContentType;
|
||||||
|
private String allowedFrameSrc;
|
||||||
|
|
||||||
|
public SecurityHeadersOptions allowFrameSrc(String source) {
|
||||||
|
allowedFrameSrc = source;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityHeadersOptions allowAnyFrameAncestor() {
|
||||||
|
allowAnyFrameAncestor = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecurityHeadersOptions skipHeaders() {
|
||||||
|
skipHeaders = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityHeadersOptions allowEmptyContentType() {
|
||||||
|
allowEmptyContentType = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getAllowedFrameSrc() {
|
||||||
|
return allowedFrameSrc;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isAllowAnyFrameAncestor() {
|
||||||
|
return allowAnyFrameAncestor;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isSkipHeaders() {
|
||||||
|
return skipHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllowEmptyContentType() {
|
||||||
|
return allowEmptyContentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
* 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.headers;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.BrowserSecurityHeaders;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
import javax.ws.rs.InternalServerErrorException;
|
||||||
|
import javax.ws.rs.container.ContainerRequestContext;
|
||||||
|
import javax.ws.rs.container.ContainerResponseContext;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DefaultSecurityHeadersProvider implements SecurityHeadersProvider {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(DefaultSecurityHeadersProvider.class);
|
||||||
|
|
||||||
|
private final Map<String, String> headerValues;
|
||||||
|
private final KeycloakSession session;
|
||||||
|
|
||||||
|
private DefaultSecurityHeadersOptions options;
|
||||||
|
|
||||||
|
public DefaultSecurityHeadersProvider(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
if (realm != null) {
|
||||||
|
headerValues = realm.getBrowserSecurityHeaders();
|
||||||
|
} else {
|
||||||
|
headerValues = BrowserSecurityHeaders.defaultHeaders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityHeadersOptions options() {
|
||||||
|
if (options == null) {
|
||||||
|
options = new DefaultSecurityHeadersOptions();
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHeaders(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
|
||||||
|
if (options != null && options.isSkipHeaders()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaType requestType = requestContext.getMediaType();
|
||||||
|
MediaType responseType = responseContext.getMediaType();
|
||||||
|
MultivaluedMap<String, Object> headers = responseContext.getHeaders();
|
||||||
|
|
||||||
|
if (responseType == null && !isEmptyMediaTypeAllowed(requestContext, responseContext)) {
|
||||||
|
LOGGER.errorv("MediaType not set on path {0}, with response status {1}", session.getContext().getUri().getRequestUri().getPath(), responseContext.getStatus());
|
||||||
|
throw new InternalServerErrorException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRest(requestType, responseType)) {
|
||||||
|
addRestHeaders(headers);
|
||||||
|
} else if (isHtml(requestType, responseType)) {
|
||||||
|
addHtmlHeaders(headers);
|
||||||
|
} else {
|
||||||
|
addGenericHeaders(headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addGenericHeaders(MultivaluedMap<String, Object> headers) {
|
||||||
|
addHeader(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY_KEY, headers);
|
||||||
|
addHeader(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS_KEY, headers);
|
||||||
|
addHeader(BrowserSecurityHeaders.X_XSS_PROTECTION_KEY, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRestHeaders(MultivaluedMap<String, Object> headers) {
|
||||||
|
addHeader(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY_KEY, headers);
|
||||||
|
addHeader(BrowserSecurityHeaders.X_FRAME_OPTIONS_KEY, headers);
|
||||||
|
addHeader(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS_KEY, headers);
|
||||||
|
addHeader(BrowserSecurityHeaders.X_XSS_PROTECTION_KEY, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addHtmlHeaders(MultivaluedMap<String, Object> headers) {
|
||||||
|
BrowserSecurityHeaders.headerAttributeMap.keySet().forEach(k -> addHeader(k, headers));
|
||||||
|
|
||||||
|
// TODO This will be refactored as part of introducing a more strict CSP header
|
||||||
|
if (options != null) {
|
||||||
|
BrowserSecurityHeaders.ContentSecurityPolicyBuilder csp = BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create();
|
||||||
|
|
||||||
|
if (options.isAllowAnyFrameAncestor()) {
|
||||||
|
headers.remove(BrowserSecurityHeaders.X_FRAME_OPTIONS);
|
||||||
|
|
||||||
|
csp.frameAncestors(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String allowedFrameSrc = options.getAllowedFrameSrc();
|
||||||
|
if (allowedFrameSrc != null) {
|
||||||
|
csp.frameSrc(allowedFrameSrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BrowserSecurityHeaders.CONTENT_SECURITY_POLICY_DEFAULT.equals(headers.getFirst(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY))) {
|
||||||
|
headers.putSingle(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY, csp.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addHeader(String key, MultivaluedMap<String, Object> headers) {
|
||||||
|
String header = BrowserSecurityHeaders.headerAttributeMap.get(key);
|
||||||
|
String value = headerValues.get(key);
|
||||||
|
if (value != null && !value.isEmpty()) {
|
||||||
|
headers.putSingle(header, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent responses without content-type unless explicitly safe to do so
|
||||||
|
*/
|
||||||
|
private boolean isEmptyMediaTypeAllowed(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
|
||||||
|
if (!responseContext.hasEntity()) {
|
||||||
|
if (options != null && options.isAllowEmptyContentType()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int status = responseContext.getStatus();
|
||||||
|
if (status == 201 || status == 204 || status == 301 || status == 302 || status == 303 || status == 400 || status == 401 || status == 403 || status == 404) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (requestContext.getMethod().equalsIgnoreCase("OPTIONS")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRest(MediaType requestType, MediaType responseType) {
|
||||||
|
MediaType mediaType = responseType != null ? responseType : requestType;
|
||||||
|
return matches(mediaType, MediaType.APPLICATION_JSON_TYPE) || matches(mediaType, MediaType.APPLICATION_XML_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHtml(MediaType requestType, MediaType responseType) {
|
||||||
|
if (matches(responseType, MediaType.TEXT_HTML_TYPE)) {
|
||||||
|
return true;
|
||||||
|
} else if (matches(requestType, MediaType.APPLICATION_FORM_URLENCODED_TYPE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matches(MediaType a, MediaType b) {
|
||||||
|
if (a == null) {
|
||||||
|
return b == null;
|
||||||
|
}
|
||||||
|
return a.getType().equalsIgnoreCase(b.getType()) && a.getSubtype().equalsIgnoreCase(b.getSubtype());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.headers;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
public class DefaultSecurityHeadersProviderFactory implements SecurityHeadersProviderFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityHeadersProvider create(KeycloakSession session) {
|
||||||
|
return new DefaultSecurityHeadersProvider(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -26,7 +26,9 @@ import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.headers.SecurityHeadersProvider;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
import org.keycloak.models.BrowserSecurityHeaders;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionContext;
|
import org.keycloak.models.ClientSessionContext;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -47,6 +49,7 @@ import org.keycloak.protocol.oidc.utils.OAuth2CodeParser;
|
||||||
import org.keycloak.services.managers.ResourceAdminManager;
|
import org.keycloak.services.managers.ResourceAdminManager;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
import org.keycloak.util.TokenUtil;
|
import org.keycloak.util.TokenUtil;
|
||||||
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -335,6 +338,8 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
||||||
uriBuilder.queryParam(STATE_PARAM, state);
|
uriBuilder.queryParam(STATE_PARAM, state);
|
||||||
return Response.status(302).location(uriBuilder.build()).build();
|
return Response.status(302).location(uriBuilder.build()).build();
|
||||||
} else {
|
} else {
|
||||||
|
// TODO Empty content with ok makes no sense. Should it display a page? Or use noContent?
|
||||||
|
session.getProvider(SecurityHeadersProvider.class).options().allowEmptyContentType();
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.protocol.oidc.endpoints;
|
||||||
|
|
||||||
import org.keycloak.common.Version;
|
import org.keycloak.common.Version;
|
||||||
import org.keycloak.common.util.UriUtils;
|
import org.keycloak.common.util.UriUtils;
|
||||||
|
import org.keycloak.headers.SecurityHeadersProvider;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -29,7 +30,6 @@ import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.CacheControl;
|
import javax.ws.rs.core.CacheControl;
|
||||||
|
@ -63,6 +63,7 @@ public class LoginStatusIframeEndpoint {
|
||||||
InputStream resource = getClass().getClassLoader().getResourceAsStream("login-status-iframe.html");
|
InputStream resource = getClass().getClassLoader().getResourceAsStream("login-status-iframe.html");
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
P3PHelper.addP3PHeader();
|
P3PHelper.addP3PHeader();
|
||||||
|
session.getProvider(SecurityHeadersProvider.class).options().allowAnyFrameAncestor();
|
||||||
return Response.ok(resource).cacheControl(cacheControl).build();
|
return Response.ok(resource).cacheControl(cacheControl).build();
|
||||||
} else {
|
} else {
|
||||||
return Response.status(Response.Status.NOT_FOUND).build();
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.headers.SecurityHeadersProvider;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -157,6 +158,8 @@ public class LogoutEndpoint {
|
||||||
if (state != null) uriBuilder.queryParam(OIDCLoginProtocol.STATE_PARAM, state);
|
if (state != null) uriBuilder.queryParam(OIDCLoginProtocol.STATE_PARAM, state);
|
||||||
return Response.status(302).location(uriBuilder.build()).build();
|
return Response.status(302).location(uriBuilder.build()).build();
|
||||||
} else {
|
} else {
|
||||||
|
// TODO Empty content with ok makes no sense. Should it display a page? Or use noContent?
|
||||||
|
session.getProvider(SecurityHeadersProvider.class).options().allowEmptyContentType();
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakUriInfo;
|
import org.keycloak.models.KeycloakUriInfo;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
import org.keycloak.urls.UrlType;
|
import org.keycloak.urls.UrlType;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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.services.filters;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.Resteasy;
|
||||||
|
import org.keycloak.headers.SecurityHeadersProvider;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
import javax.ws.rs.container.ContainerRequestContext;
|
||||||
|
import javax.ws.rs.container.ContainerResponseContext;
|
||||||
|
import javax.ws.rs.container.ContainerResponseFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class KeycloakSecurityHeadersFilter implements ContainerResponseFilter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
|
||||||
|
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
|
||||||
|
SecurityHeadersProvider securityHeadersProvider = session.getProvider(SecurityHeadersProvider.class);
|
||||||
|
securityHeadersProvider.addHeaders(requestContext, responseContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -42,6 +42,7 @@ import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.services.DefaultKeycloakSessionFactory;
|
import org.keycloak.services.DefaultKeycloakSessionFactory;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.error.KeycloakErrorHandler;
|
import org.keycloak.services.error.KeycloakErrorHandler;
|
||||||
|
import org.keycloak.services.filters.KeycloakSecurityHeadersFilter;
|
||||||
import org.keycloak.services.filters.KeycloakTransactionCommitter;
|
import org.keycloak.services.filters.KeycloakTransactionCommitter;
|
||||||
import org.keycloak.services.managers.ApplianceBootstrap;
|
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
@ -114,6 +115,7 @@ public class KeycloakApplication extends Application {
|
||||||
classes.add(ThemeResource.class);
|
classes.add(ThemeResource.class);
|
||||||
classes.add(JsResource.class);
|
classes.add(JsResource.class);
|
||||||
|
|
||||||
|
classes.add(KeycloakSecurityHeadersFilter.class);
|
||||||
classes.add(KeycloakTransactionCommitter.class);
|
classes.add(KeycloakTransactionCommitter.class);
|
||||||
classes.add(KeycloakErrorHandler.class);
|
classes.add(KeycloakErrorHandler.class);
|
||||||
|
|
||||||
|
|
|
@ -21,16 +21,13 @@ import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.common.Version;
|
import org.keycloak.common.Version;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.common.util.MimeTypeUtil;
|
import org.keycloak.common.util.MimeTypeUtil;
|
||||||
import org.keycloak.models.BrowserSecurityHeaders;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.services.ForbiddenException;
|
import org.keycloak.services.ForbiddenException;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.Urls;
|
|
||||||
import org.keycloak.services.managers.ApplianceBootstrap;
|
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||||
import org.keycloak.services.util.CacheControlUtil;
|
import org.keycloak.services.util.CacheControlUtil;
|
||||||
import org.keycloak.services.util.CookieHelper;
|
import org.keycloak.services.util.CookieHelper;
|
||||||
import org.keycloak.theme.BrowserSecurityHeaderSetup;
|
|
||||||
import org.keycloak.theme.FreeMarkerUtil;
|
import org.keycloak.theme.FreeMarkerUtil;
|
||||||
import org.keycloak.theme.Theme;
|
import org.keycloak.theme.Theme;
|
||||||
import org.keycloak.urls.UrlType;
|
import org.keycloak.urls.UrlType;
|
||||||
|
@ -210,7 +207,6 @@ public class WelcomeResource {
|
||||||
ResponseBuilder rb = Response.status(errorMessage == null ? Status.OK : Status.BAD_REQUEST)
|
ResponseBuilder rb = Response.status(errorMessage == null ? Status.OK : Status.BAD_REQUEST)
|
||||||
.entity(result)
|
.entity(result)
|
||||||
.cacheControl(CacheControlUtil.noCache());
|
.cacheControl(CacheControlUtil.noCache());
|
||||||
BrowserSecurityHeaderSetup.headers(rb);
|
|
||||||
return rb.build();
|
return rb.build();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
|
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
|
||||||
|
|
|
@ -2,7 +2,6 @@ package org.keycloak.services.resources.account;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.keycloak.common.Profile;
|
|
||||||
import org.keycloak.common.Version;
|
import org.keycloak.common.Version;
|
||||||
import org.keycloak.events.EventStoreProvider;
|
import org.keycloak.events.EventStoreProvider;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
@ -15,11 +14,8 @@ import org.keycloak.services.Urls;
|
||||||
import org.keycloak.services.managers.AppAuthManager;
|
import org.keycloak.services.managers.AppAuthManager;
|
||||||
import org.keycloak.services.managers.Auth;
|
import org.keycloak.services.managers.Auth;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.ClientManager;
|
|
||||||
import org.keycloak.services.managers.RealmManager;
|
|
||||||
import org.keycloak.services.util.ResolveRelative;
|
import org.keycloak.services.util.ResolveRelative;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
import org.keycloak.theme.BrowserSecurityHeaderSetup;
|
|
||||||
import org.keycloak.theme.FreeMarkerException;
|
import org.keycloak.theme.FreeMarkerException;
|
||||||
import org.keycloak.theme.FreeMarkerUtil;
|
import org.keycloak.theme.FreeMarkerUtil;
|
||||||
import org.keycloak.theme.Theme;
|
import org.keycloak.theme.Theme;
|
||||||
|
@ -31,12 +27,10 @@ import javax.json.Json;
|
||||||
import javax.json.JsonObjectBuilder;
|
import javax.json.JsonObjectBuilder;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -124,7 +118,6 @@ public class AccountConsole {
|
||||||
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
|
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
|
||||||
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
|
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
|
||||||
Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML_UTF_8).language(Locale.ENGLISH).entity(result);
|
Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML_UTF_8).language(Locale.ENGLISH).entity(result);
|
||||||
BrowserSecurityHeaderSetup.headers(builder, realm);
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -395,7 +395,7 @@ public class AccountCredentialResource {
|
||||||
|
|
||||||
event.client(auth.getClient()).user(auth.getUser()).success();
|
event.client(auth.getClient()).user(auth.getUser()).success();
|
||||||
|
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PasswordDetails {
|
public static class PasswordDetails {
|
||||||
|
|
|
@ -217,7 +217,7 @@ public class AccountRestService {
|
||||||
|
|
||||||
event.success();
|
event.success();
|
||||||
|
|
||||||
return Cors.add(request, Response.ok()).auth().allowedOrigins(auth.getToken()).build();
|
return Cors.add(request, Response.noContent()).auth().allowedOrigins(auth.getToken()).build();
|
||||||
} catch (ReadOnlyException e) {
|
} catch (ReadOnlyException e) {
|
||||||
return ErrorResponse.error(Messages.READ_ONLY_USER, Response.Status.BAD_REQUEST);
|
return ErrorResponse.error(Messages.READ_ONLY_USER, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
@ -364,7 +364,7 @@ public class AccountRestService {
|
||||||
session.users().revokeConsentForClient(realm, user.getId(), client.getId());
|
session.users().revokeConsentForClient(realm, user.getId(), client.getId());
|
||||||
event.success();
|
event.success();
|
||||||
|
|
||||||
return Cors.add(request, Response.accepted()).build();
|
return Cors.add(request, Response.noContent()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -407,7 +407,7 @@ public class AccountRestService {
|
||||||
session.userCredentialManager().disableCredentialType(realm, user, CredentialModel.OTP);
|
session.userCredentialManager().disableCredentialType(realm, user, CredentialModel.OTP);
|
||||||
event.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
|
event.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
|
||||||
|
|
||||||
return Cors.add(request, Response.accepted()).build();
|
return Cors.add(request, Response.noContent()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -233,7 +233,7 @@ public class LinkedAccountsResource {
|
||||||
.detail(Details.IDENTITY_PROVIDER_USERNAME, link.getUserName())
|
.detail(Details.IDENTITY_PROVIDER_USERNAME, link.getUserName())
|
||||||
.success();
|
.success();
|
||||||
|
|
||||||
return Cors.add(request, Response.ok()).auth().allowedOrigins(auth.getToken()).build();
|
return Cors.add(request, Response.noContent()).auth().allowedOrigins(auth.getToken()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String checkCommonPreconditions(String providerId) {
|
private String checkCommonPreconditions(String providerId) {
|
||||||
|
|
|
@ -25,7 +25,7 @@ import javax.ws.rs.NotFoundException;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.common.Version;
|
import org.keycloak.common.Version;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.headers.SecurityHeadersProvider;
|
||||||
import org.keycloak.models.AdminRoles;
|
import org.keycloak.models.AdminRoles;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -34,13 +34,11 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||||
import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
|
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
import org.keycloak.services.managers.AppAuthManager;
|
import org.keycloak.services.managers.AppAuthManager;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.ClientManager;
|
import org.keycloak.services.managers.ClientManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.theme.BrowserSecurityHeaderSetup;
|
|
||||||
import org.keycloak.theme.FreeMarkerException;
|
import org.keycloak.theme.FreeMarkerException;
|
||||||
import org.keycloak.theme.FreeMarkerUtil;
|
import org.keycloak.theme.FreeMarkerUtil;
|
||||||
import org.keycloak.theme.Theme;
|
import org.keycloak.theme.Theme;
|
||||||
|
@ -55,7 +53,6 @@ import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import javax.ws.rs.ext.Providers;
|
import javax.ws.rs.ext.Providers;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -309,15 +306,11 @@ public class AdminConsole {
|
||||||
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
|
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
|
||||||
Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML_UTF_8).language(Locale.ENGLISH).entity(result);
|
Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML_UTF_8).language(Locale.ENGLISH).entity(result);
|
||||||
|
|
||||||
BrowserSecurityHeaderSetup.Options headerOptions = null;
|
|
||||||
|
|
||||||
// Replace CSP if admin is hosted on different URL
|
// Replace CSP if admin is hosted on different URL
|
||||||
if (!adminBaseUri.equals(authServerBaseUri)) {
|
if (!adminBaseUri.equals(authServerBaseUri)) {
|
||||||
headerOptions = BrowserSecurityHeaderSetup.Options.create().allowFrameSrc(UriBuilder.fromUri(authServerBaseUri).replacePath("").build().toString()).build();
|
session.getProvider(SecurityHeadersProvider.class).options().allowFrameSrc(UriBuilder.fromUri(authServerBaseUri).replacePath("").build().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
BrowserSecurityHeaderSetup.headers(builder, realm, headerOptions);
|
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -830,7 +830,7 @@ public class UserResource {
|
||||||
|
|
||||||
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).success();
|
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).success();
|
||||||
|
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
} catch (EmailException e) {
|
} catch (EmailException e) {
|
||||||
ServicesLogger.LOGGER.failedToSendActionsEmail(e);
|
ServicesLogger.LOGGER.failedToSendActionsEmail(e);
|
||||||
return ErrorResponse.error("Failed to send execute actions email", Status.INTERNAL_SERVER_ERROR);
|
return ErrorResponse.error("Failed to send execute actions email", Status.INTERNAL_SERVER_ERROR);
|
||||||
|
|
|
@ -27,7 +27,6 @@ import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
import org.keycloak.theme.BrowserSecurityHeaderSetup;
|
|
||||||
import org.keycloak.utils.MediaType;
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -181,7 +180,6 @@ public abstract class BrowserHistoryHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
Response.ResponseBuilder builder = Response.status(200).type(MediaType.TEXT_HTML_UTF_8).entity(savedResponse);
|
Response.ResponseBuilder builder = Response.status(200).type(MediaType.TEXT_HTML_UTF_8).entity(savedResponse);
|
||||||
BrowserSecurityHeaderSetup.headers(builder, session.getContext().getRealm()); // TODO rather all the headers from the saved response should be added here.
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.theme;
|
|
||||||
|
|
||||||
import org.keycloak.models.BrowserSecurityHeaders;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
|
|
||||||
import javax.swing.text.html.Option;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class BrowserSecurityHeaderSetup {
|
|
||||||
|
|
||||||
public static class Options {
|
|
||||||
|
|
||||||
private String allowedFrameSrc;
|
|
||||||
|
|
||||||
public static Options create() {
|
|
||||||
return new Options();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Options allowFrameSrc(String source) {
|
|
||||||
allowedFrameSrc = source;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Options build() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm) {
|
|
||||||
return headers(builder, realm.getBrowserSecurityHeaders(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm, Options options) {
|
|
||||||
return headers(builder, realm.getBrowserSecurityHeaders(), options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder) {
|
|
||||||
return headers(builder, BrowserSecurityHeaders.defaultHeaders, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Response.ResponseBuilder headers(Response.ResponseBuilder builder, Map<String, String> headers, Options options) {
|
|
||||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
|
||||||
String header = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
|
|
||||||
String value = entry.getValue();
|
|
||||||
|
|
||||||
if (options != null) {
|
|
||||||
if (header.equals(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY) && value.equals(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY_DEFAULT) && options.allowedFrameSrc != null) {
|
|
||||||
value = "frame-src " + options.allowedFrameSrc + "; frame-ancestors 'self'; object-src 'none';";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header != null && value != null && !value.isEmpty()) {
|
|
||||||
builder.header(header, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.headers.DefaultSecurityHeadersProviderFactory
|
|
@ -140,7 +140,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
session.sessions().removeUserSession(realm, sessionModel);
|
session.sessions().removeUserSession(realm, sessionModel);
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
|
@ -150,7 +150,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
RealmModel realm = getRealmByName(realmName);
|
RealmModel realm = getRealmByName(realmName);
|
||||||
|
|
||||||
session.sessions().removeUserSessions(realm);
|
session.sessions().removeUserSessions(realm);
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -177,7 +177,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
session.authenticationSessions().removeExpired(realm);
|
session.authenticationSessions().removeExpired(realm);
|
||||||
session.realms().removeExpiredClientInitialAccess();
|
session.realms().removeExpiredClientInitialAccess();
|
||||||
|
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -251,7 +251,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response clearEventQueue() {
|
public Response clearEventQueue() {
|
||||||
EventsListenerProvider.clear();
|
EventsListenerProvider.clear();
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
|
@ -259,7 +259,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response clearAdminEventQueue() {
|
public Response clearAdminEventQueue() {
|
||||||
EventsListenerProvider.clearAdminEvents();
|
EventsListenerProvider.clearAdminEvents();
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -268,7 +268,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
public Response clearEventStore() {
|
public Response clearEventStore() {
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
eventStore.clear();
|
eventStore.clear();
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -277,7 +277,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
public Response clearEventStore(@QueryParam("realmId") String realmId) {
|
public Response clearEventStore(@QueryParam("realmId") String realmId) {
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
eventStore.clear(realmId);
|
eventStore.clear(realmId);
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -286,7 +286,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
public Response clearEventStore(@QueryParam("realmId") String realmId, @QueryParam("olderThan") long olderThan) {
|
public Response clearEventStore(@QueryParam("realmId") String realmId, @QueryParam("olderThan") long olderThan) {
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
eventStore.clear(realmId, olderThan);
|
eventStore.clear(realmId, olderThan);
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -398,7 +398,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
public Response clearAdminEventStore() {
|
public Response clearAdminEventStore() {
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
eventStore.clearAdmin();
|
eventStore.clearAdmin();
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -407,7 +407,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
public Response clearAdminEventStore(@QueryParam("realmId") String realmId) {
|
public Response clearAdminEventStore(@QueryParam("realmId") String realmId) {
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
eventStore.clearAdmin(realmId);
|
eventStore.clearAdmin(realmId);
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -416,7 +416,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
public Response clearAdminEventStore(@QueryParam("realmId") String realmId, @QueryParam("olderThan") long olderThan) {
|
public Response clearAdminEventStore(@QueryParam("realmId") String realmId, @QueryParam("olderThan") long olderThan) {
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
eventStore.clearAdmin(realmId, olderThan);
|
eventStore.clearAdmin(realmId, olderThan);
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -830,7 +830,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
|
|
||||||
@Path("/javascript")
|
@Path("/javascript")
|
||||||
public TestJavascriptResource getJavascriptResource() {
|
public TestJavascriptResource getJavascriptResource() {
|
||||||
return new TestJavascriptResource();
|
return new TestJavascriptResource(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setFeatureInProfileFile(File file, Profile.Feature featureProfile, String newState) {
|
private void setFeatureInProfileFile(File file, Profile.Feature featureProfile, String newState) {
|
||||||
|
@ -866,7 +866,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Profile.isFeatureEnabled(featureProfile))
|
if (Profile.isFeatureEnabled(featureProfile))
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
|
|
||||||
FeatureDeployerUtil.initBeforeChangeFeature(featureProfile);
|
FeatureDeployerUtil.initBeforeChangeFeature(featureProfile);
|
||||||
|
|
||||||
|
@ -883,7 +883,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
FeatureDeployerUtil.deployFactoriesAfterFeatureEnabled(featureProfile);
|
FeatureDeployerUtil.deployFactoriesAfterFeatureEnabled(featureProfile);
|
||||||
|
|
||||||
if (Profile.isFeatureEnabled(featureProfile))
|
if (Profile.isFeatureEnabled(featureProfile))
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
else
|
else
|
||||||
return Response.status(Response.Status.NOT_FOUND).build();
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
|
@ -902,7 +902,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Profile.isFeatureEnabled(featureProfile))
|
if (!Profile.isFeatureEnabled(featureProfile))
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
|
|
||||||
FeatureDeployerUtil.initBeforeChangeFeature(featureProfile);
|
FeatureDeployerUtil.initBeforeChangeFeature(featureProfile);
|
||||||
|
|
||||||
|
@ -919,7 +919,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
FeatureDeployerUtil.undeployFactoriesAfterFeatureDisabled(featureProfile);
|
FeatureDeployerUtil.undeployFactoriesAfterFeatureDisabled(featureProfile);
|
||||||
|
|
||||||
if (!Profile.isFeatureEnabled(featureProfile))
|
if (!Profile.isFeatureEnabled(featureProfile))
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
else
|
else
|
||||||
return Response.status(Response.Status.NOT_FOUND).build();
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.keycloak.testsuite.rest.resource;
|
package org.keycloak.testsuite.rest.resource;
|
||||||
|
|
||||||
|
import org.keycloak.headers.SecurityHeadersProvider;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.testsuite.rest.TestingResourceProvider;
|
import org.keycloak.testsuite.rest.TestingResourceProvider;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
@ -16,6 +18,12 @@ import java.io.InputStreamReader;
|
||||||
*/
|
*/
|
||||||
public class TestJavascriptResource {
|
public class TestJavascriptResource {
|
||||||
|
|
||||||
|
private KeycloakSession session;
|
||||||
|
|
||||||
|
public TestJavascriptResource(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/js/keycloak.js")
|
@Path("/js/keycloak.js")
|
||||||
@Produces("application/javascript")
|
@Produces("application/javascript")
|
||||||
|
@ -27,6 +35,7 @@ public class TestJavascriptResource {
|
||||||
@Path("/index.html")
|
@Path("/index.html")
|
||||||
@Produces(MediaType.TEXT_HTML)
|
@Produces(MediaType.TEXT_HTML)
|
||||||
public String getJavascriptTestingEnvironment() throws IOException {
|
public String getJavascriptTestingEnvironment() throws IOException {
|
||||||
|
session.getProvider(SecurityHeadersProvider.class).options().skipHeaders();
|
||||||
return resourceToString("/javascript/index.html");
|
return resourceToString("/javascript/index.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ public class TestingExportImportResource {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response runImport() {
|
public Response runImport() {
|
||||||
new ExportImportManager(session).runImport();
|
new ExportImportManager(session).runImport();
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -64,7 +64,7 @@ public class TestingExportImportResource {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response runExport() {
|
public Response runExport() {
|
||||||
new ExportImportManager(session).runExport();
|
new ExportImportManager(session).runExport();
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -157,6 +157,6 @@ public class TestingExportImportResource {
|
||||||
System.clearProperty(ACTION);
|
System.clearProperty(ACTION);
|
||||||
System.clearProperty(FILE);
|
System.clearProperty(FILE);
|
||||||
|
|
||||||
return Response.ok().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,13 +77,13 @@ public class KeycloakTestingClient implements AutoCloseable {
|
||||||
|
|
||||||
public void enableFeature(Profile.Feature feature) {
|
public void enableFeature(Profile.Feature feature) {
|
||||||
try (Response response = testing().enableFeature(feature.toString())) {
|
try (Response response = testing().enableFeature(feature.toString())) {
|
||||||
assertEquals(200, response.getStatus());
|
assertEquals(204, response.getStatus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disableFeature(Profile.Feature feature) {
|
public void disableFeature(Profile.Feature feature) {
|
||||||
try (Response response = testing().disableFeature(feature.toString())) {
|
try (Response response = testing().disableFeature(feature.toString())) {
|
||||||
assertEquals(200, response.getStatus());
|
assertEquals(204, response.getStatus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ public class AccountRestServiceCorsTest extends AbstractTestRealmKeycloakTest {
|
||||||
error = t;
|
error = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == null || result.getStatus() != 200 || error != null) {
|
if (result == null || (result.getStatus() != 200 && result.getStatus() != 204) || error != null) {
|
||||||
if (expectAllowed) {
|
if (expectAllowed) {
|
||||||
throw new AssertionError("Cors request failed: " + WebDriverLogDumper.dumpBrowserLogs(driver));
|
throw new AssertionError("Cors request failed: " + WebDriverLogDumper.dumpBrowserLogs(driver));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -174,7 +174,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
user.setAttributes(originalAttributes);
|
user.setAttributes(originalAttributes);
|
||||||
SimpleHttp.Response response = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asResponse();
|
SimpleHttp.Response response = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asResponse();
|
||||||
System.out.println(response.asString());
|
System.out.println(response.asString());
|
||||||
assertEquals(200, response.getStatus());
|
assertEquals(204, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -198,13 +198,13 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
} finally {
|
} finally {
|
||||||
user.setFirstName(originalFirstname);
|
user.setFirstName(originalFirstname);
|
||||||
int status = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asStatus();
|
int status = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asStatus();
|
||||||
assertEquals(200, status);
|
assertEquals(204, status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserRepresentation updateAndGet(UserRepresentation user) throws IOException {
|
private UserRepresentation updateAndGet(UserRepresentation user) throws IOException {
|
||||||
int status = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asStatus();
|
int status = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asStatus();
|
||||||
assertEquals(200, status);
|
assertEquals(204, status);
|
||||||
return SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
|
return SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +270,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
events.poll();
|
events.poll();
|
||||||
|
|
||||||
//Change the password
|
//Change the password
|
||||||
updatePassword("password", "Str0ng3rP4ssw0rd", 200);
|
updatePassword("password", "Str0ng3rP4ssw0rd", 204);
|
||||||
|
|
||||||
//Get the new value for lastUpdate
|
//Get the new value for lastUpdate
|
||||||
AccountCredentialResource.PasswordDetails updatedDetails = getPasswordDetails();
|
AccountCredentialResource.PasswordDetails updatedDetails = getPasswordDetails();
|
||||||
|
@ -285,17 +285,17 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
assertEquals(updatedDetails.getLastUpdate(), finalDetails.getLastUpdate());
|
assertEquals(updatedDetails.getLastUpdate(), finalDetails.getLastUpdate());
|
||||||
|
|
||||||
//Change the password back
|
//Change the password back
|
||||||
updatePassword("Str0ng3rP4ssw0rd", "password", 200);
|
updatePassword("Str0ng3rP4ssw0rd", "password", 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPasswordConfirmation() throws IOException {
|
public void testPasswordConfirmation() throws IOException {
|
||||||
updatePassword("password", "Str0ng3rP4ssw0rd", "confirmationDoesNotMatch", 400);
|
updatePassword("password", "Str0ng3rP4ssw0rd", "confirmationDoesNotMatch", 400);
|
||||||
|
|
||||||
updatePassword("password", "Str0ng3rP4ssw0rd", "Str0ng3rP4ssw0rd", 200);
|
updatePassword("password", "Str0ng3rP4ssw0rd", "Str0ng3rP4ssw0rd", 204);
|
||||||
|
|
||||||
//Change the password back
|
//Change the password back
|
||||||
updatePassword("Str0ng3rP4ssw0rd", "password", 200);
|
updatePassword("Str0ng3rP4ssw0rd", "password", 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AccountCredentialResource.PasswordDetails getPasswordDetails() throws IOException {
|
private AccountCredentialResource.PasswordDetails getPasswordDetails() throws IOException {
|
||||||
|
@ -1090,14 +1090,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.auth(token.getToken())
|
.auth(token.getToken())
|
||||||
.asResponse();
|
.asResponse();
|
||||||
assertEquals(202, response.getStatus());
|
assertEquals(204, response.getStatus());
|
||||||
|
|
||||||
response = SimpleHttp
|
response = SimpleHttp
|
||||||
.doDelete(getAccountUrl("applications/" + appId + "/consent"), httpClient)
|
.doDelete(getAccountUrl("applications/" + appId + "/consent"), httpClient)
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.auth(token.getToken())
|
.auth(token.getToken())
|
||||||
.asResponse();
|
.asResponse();
|
||||||
assertEquals(202, response.getStatus());
|
assertEquals(204, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.keycloak.testsuite.admin;
|
||||||
|
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpOptions;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.models.BrowserSecurityHeaders;
|
||||||
|
import org.keycloak.services.resources.Cors;
|
||||||
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class AdminHeadersTest extends AbstractAdminTest {
|
||||||
|
|
||||||
|
|
||||||
|
private CloseableHttpClient client;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
client = HttpClientBuilder.create().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
try {
|
||||||
|
client.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHeaders() {
|
||||||
|
Response response = realm.users().create(UserBuilder.create().username("headers-user").build());
|
||||||
|
MultivaluedMap<String, Object> h = response.getHeaders();
|
||||||
|
|
||||||
|
assertEquals(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY_DEFAULT, h.getFirst(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY));
|
||||||
|
assertEquals(BrowserSecurityHeaders.X_FRAME_OPTIONS_DEFAULT, h.getFirst(BrowserSecurityHeaders.X_FRAME_OPTIONS));
|
||||||
|
assertEquals(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS_DEFAULT, h.getFirst(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS));
|
||||||
|
assertEquals(BrowserSecurityHeaders.X_XSS_PROTECTION_DEFAULT, h.getFirst(BrowserSecurityHeaders.X_XSS_PROTECTION));
|
||||||
|
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAdminUrl(String resource) {
|
||||||
|
return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/admin/" + resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -232,9 +232,10 @@ public class GroupPolicyManagementTest extends AbstractPolicyManagementTest {
|
||||||
|
|
||||||
groups.group(group.getId()).remove();
|
groups.group(group.getId()).remove();
|
||||||
|
|
||||||
GroupPolicyRepresentation policy = getClient().authorization().policies().group().findByName(representation.getName());
|
try {
|
||||||
|
getClient().authorization().policies().group().findByName(representation.getName());
|
||||||
assertNull(policy);
|
} catch (NotFoundException e) {
|
||||||
|
}
|
||||||
|
|
||||||
representation.getGroups().clear();
|
representation.getGroups().clear();
|
||||||
representation.addGroupPath("/Group H/Group I/Group K");
|
representation.addGroupPath("/Group H/Group I/Group K");
|
||||||
|
@ -246,7 +247,7 @@ public class GroupPolicyManagementTest extends AbstractPolicyManagementTest {
|
||||||
|
|
||||||
groups.group(group.getId()).remove();
|
groups.group(group.getId()).remove();
|
||||||
|
|
||||||
policy = getClient().authorization().policies().group().findByName(representation.getName());
|
GroupPolicyRepresentation policy = getClient().authorization().policies().group().findByName(representation.getName());
|
||||||
|
|
||||||
assertNotNull(policy);
|
assertNotNull(policy);
|
||||||
assertEquals(1, policy.getGroups().size());
|
assertEquals(1, policy.getGroups().size());
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.common.util.StreamUtil;
|
import org.keycloak.common.util.StreamUtil;
|
||||||
|
import org.keycloak.models.BrowserSecurityHeaders;
|
||||||
import org.keycloak.representations.idm.ErrorRepresentation;
|
import org.keycloak.representations.idm.ErrorRepresentation;
|
||||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
@ -20,6 +21,7 @@ import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
|
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
|
||||||
import org.keycloak.testsuite.pages.ErrorPage;
|
import org.keycloak.testsuite.pages.ErrorPage;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -29,6 +31,7 @@ import java.net.URI;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
@ -115,6 +118,27 @@ public class UncaughtErrorPageTest extends AbstractKeycloakTest {
|
||||||
assertEquals("An internal server error has occurred", errorPage.getError());
|
assertEquals("An internal server error has occurred", errorPage.getError());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UncaughtServerErrorExpected
|
||||||
|
public void uncaughtErrorHeaders() throws IOException {
|
||||||
|
URI uri = suiteContext.getAuthServerInfo().getUriBuilder().path("/auth/realms/master/testing/uncaught-error").build();
|
||||||
|
|
||||||
|
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
|
||||||
|
SimpleHttp.Response response = SimpleHttp.doGet(uri.toString(), client).header("Accept", MediaType.TEXT_HTML_UTF_8).asResponse();
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> e : BrowserSecurityHeaders.headerAttributeMap.entrySet()) {
|
||||||
|
String header = e.getValue();
|
||||||
|
String expectedValue = BrowserSecurityHeaders.defaultHeaders.get(e.getKey());
|
||||||
|
|
||||||
|
if (expectedValue == null || expectedValue.isEmpty()) {
|
||||||
|
assertNull(response.getFirstHeader(header));
|
||||||
|
} else {
|
||||||
|
assertEquals(expectedValue, response.getFirstHeader(header));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void errorPageException() {
|
public void errorPageException() {
|
||||||
oauth.realm("master");
|
oauth.realm("master");
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.apache.http.impl.client.HttpClients;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
|
import org.keycloak.models.BrowserSecurityHeaders;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
@ -50,6 +51,7 @@ import java.util.List;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||||
|
|
||||||
|
@ -128,6 +130,9 @@ public class LoginStatusIframeEndpointTest extends AbstractKeycloakTest {
|
||||||
assertTrue(s.contains("function getCookie()"));
|
assertTrue(s.contains("function getCookie()"));
|
||||||
|
|
||||||
assertEquals("CP=\"This is not a P3P policy!\"", response.getFirstHeader("P3P").getValue());
|
assertEquals("CP=\"This is not a P3P policy!\"", response.getFirstHeader("P3P").getValue());
|
||||||
|
assertNull(response.getFirstHeader(BrowserSecurityHeaders.X_FRAME_OPTIONS));
|
||||||
|
assertEquals("frame-src 'self'; object-src 'none';", response.getFirstHeader(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY).getValue());
|
||||||
|
assertEquals("none", response.getFirstHeader(BrowserSecurityHeaders.X_ROBOTS_TAG).getValue());
|
||||||
|
|
||||||
response.close();
|
response.close();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue