X-Frame-Options, Content-Security-Policy
This commit is contained in:
parent
e73bdf9d8c
commit
7c97e02715
25 changed files with 246 additions and 31 deletions
|
@ -52,6 +52,7 @@ public class RealmRepresentation {
|
|||
protected Map<String, List<ScopeMappingRepresentation>> applicationScopeMappings;
|
||||
protected List<ApplicationRepresentation> applications;
|
||||
protected List<OAuthClientRepresentation> oauthClients;
|
||||
protected Map<String, String> browserSecurityHeaders;
|
||||
protected Map<String, String> socialProviders;
|
||||
protected Map<String, String> smtpServer;
|
||||
protected List<UserFederationProviderRepresentation> userFederationProviders;
|
||||
|
@ -291,6 +292,14 @@ public class RealmRepresentation {
|
|||
this.updateProfileOnInitialSocialLogin = updateProfileOnInitialSocialLogin;
|
||||
}
|
||||
|
||||
public Map<String, String> getBrowserSecurityHeaders() {
|
||||
return browserSecurityHeaders;
|
||||
}
|
||||
|
||||
public void setBrowserSecurityHeaders(Map<String, String> browserSecurityHeaders) {
|
||||
this.browserSecurityHeaders = browserSecurityHeaders;
|
||||
}
|
||||
|
||||
public Map<String, String> getSocialProviders() {
|
||||
return socialProviders;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.keycloak.account.freemarker.model.SessionsBean;
|
|||
import org.keycloak.account.freemarker.model.TotpBean;
|
||||
import org.keycloak.account.freemarker.model.UrlBean;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
||||
import org.keycloak.freemarker.FreeMarkerException;
|
||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||
import org.keycloak.freemarker.Theme;
|
||||
|
@ -136,7 +137,9 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
|||
|
||||
try {
|
||||
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
|
||||
return Response.status(status).type(MediaType.TEXT_HTML).entity(result).build();
|
||||
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
|
||||
BrowserSecurityHeaderSetup.headers(builder, realm);
|
||||
return builder.build();
|
||||
} catch (FreeMarkerException e) {
|
||||
logger.error("Failed to process template", e);
|
||||
return Response.serverError().build();
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package org.keycloak.freemarker;
|
||||
|
||||
import org.keycloak.models.BrowserSecurityHeaders;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
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 Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm) {
|
||||
for (Map.Entry<String, String> entry : realm.getBrowserSecurityHeaders().entrySet()) {
|
||||
String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
|
||||
if (headerName == null) continue;
|
||||
builder.header(headerName, entry.getValue());
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
}
|
|
@ -675,16 +675,7 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
},
|
||||
controller : 'RealmRevocationCtrl'
|
||||
})
|
||||
.when('/realms/:realm/sessions/brute-force', {
|
||||
templateUrl : 'partials/session-brute-force.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmBruteForceCtrl'
|
||||
})
|
||||
.when('/realms/:realm/sessions/realm', {
|
||||
.when('/realms/:realm/sessions/realm', {
|
||||
templateUrl : 'partials/session-realm.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
|
@ -761,6 +752,28 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
},
|
||||
controller : 'GenericUserFederationCtrl'
|
||||
})
|
||||
.when('/realms/:realm/defense/headers', {
|
||||
templateUrl : 'partials/defense-headers.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
}
|
||||
|
||||
},
|
||||
controller : 'DefenseHeadersCtrl'
|
||||
})
|
||||
.when('/realms/:realm/defense/brute-force', {
|
||||
templateUrl : 'partials/brute-force.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmBruteForceCtrl'
|
||||
})
|
||||
.when('/logout', {
|
||||
templateUrl : 'partials/home.html',
|
||||
controller : 'LogoutCtrl'
|
||||
|
|
|
@ -340,8 +340,6 @@ function genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $l
|
|||
|
||||
var oldCopy = angular.copy($scope.realm);
|
||||
|
||||
|
||||
|
||||
$scope.changed = false;
|
||||
|
||||
$scope.$watch('realm', function() {
|
||||
|
@ -384,16 +382,20 @@ function genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $l
|
|||
|
||||
}
|
||||
|
||||
module.controller('DefenseHeadersCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
|
||||
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/defense/headers");
|
||||
});
|
||||
|
||||
module.controller('RealmLoginSettingsCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
|
||||
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/login-settings")
|
||||
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/login-settings");
|
||||
});
|
||||
|
||||
module.controller('RealmThemeCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
|
||||
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/theme-settings")
|
||||
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/theme-settings");
|
||||
});
|
||||
|
||||
module.controller('RealmCacheCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
|
||||
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/cache-settings")
|
||||
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/cache-settings");
|
||||
});
|
||||
|
||||
module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications, PasswordPolicy) {
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-sm-9" role="main">
|
||||
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
|
||||
<li><a href="#/realms/{{realm.realm}}/sessions/realm">Realm Sessions</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/token-settings">Timeout Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/sessions/revocation">Revocation</a></li>
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/sessions/brute-force">Brute Force Protection</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/defense/headers">Headers</a></li>
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/defense/brute-force">Brute Force Protection</a></li>
|
||||
</ul>
|
||||
<div id="content">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li class="active">Brute Force</li>
|
||||
</ol>
|
||||
<h2><span>{{realm.realm}}</span> Brute Force Protection Settings</h2>
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
<fieldset class="border-top">
|
|
@ -0,0 +1,36 @@
|
|||
<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-sm-9" role="main">
|
||||
<ul class="nav nav-tabs nav-tabs-pf">
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/defense/headers">Headers</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/defense/brute-force">Brute Force Detection</a></li>
|
||||
</ul>
|
||||
<div id="content">
|
||||
<div data-ng-show="access.viewRealm">
|
||||
<h2><span>{{realm.realm}}</span> Browser Security Headers</h2>
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="xFrameOptions"><a href="http://tools.ietf.org/html/rfc7034">X-Frame-Options</a></label>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" id="xFrameOptions" type="text" ng-model="realm.browserSecurityHeaders.xFrameOptions">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="contentSecurityPolicy"><a href="http://www.w3.org/TR/CSP/">Content-Security-Policy</a></label>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" id="contentSecurityPolicy" type="text" ng-model="realm.browserSecurityHeaders.contentSecurityPolicy">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="pull-right form-actions" data-ng-show="access.manageRealm">
|
||||
<button kc-reset data-ng-show="changed">Clear changes</button>
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div data-ng-hide="access.viewRealm">
|
||||
<h2 ><span>{{realm.realm}}</span></h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -15,17 +15,16 @@
|
|||
<fieldset class="border-top">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="name">Name <span class="required" data-ng-show="createRealm">*</span></label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" type="text" id="name" name="name" data-ng-model="realm.realm" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
|
||||
<span tooltip="Users and applications can only access a realm if it's enabled" class="pficon pficon-help"></span>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.enabled" name="enabled" id="enabled" onoffswitch />
|
||||
</div>
|
||||
<span tooltip="Users and applications can only access a realm if it's enabled" class="pficon pficon-help"></span>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
|
|
@ -9,5 +9,6 @@
|
|||
<li data-ng-show="access.viewApplications" data-ng-class="(path[2] == 'applications' || path[1] == 'application' || path[3] == 'applications') && 'active'"><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
|
||||
<li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'oauth-clients' || path[1] == 'oauth-client') && 'active'"><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
|
||||
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'sessions' || path[2] == 'token-settings') && 'active'"><a href="#/realms/{{realm.realm}}/sessions/realm">Sessions and Tokens</a></li>
|
||||
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'defense') && 'active'"><a href="#/realms/{{realm.realm}}/defense/headers">Security Defenses</a></li>
|
||||
<li data-ng-show="access.viewAudit" data-ng-class="(path[2] == 'audit' || path[2] == 'audit-settings') && 'active'"><a href="#/realms/{{realm.realm}}/audit">Audit</a></li>
|
||||
</ul>
|
|
@ -4,7 +4,6 @@
|
|||
<li class="active"><a href="#/realms/{{realm.realm}}/sessions/realm">Realm Sessions</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/token-settings">Timeout Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/sessions/revocation">Revocation</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/sessions/brute-force">Brute Force Protection</a></li>
|
||||
</ul>
|
||||
<div id="content">
|
||||
<ol class="breadcrumb">
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailProvider;
|
||||
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
||||
import org.keycloak.freemarker.FreeMarkerException;
|
||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||
import org.keycloak.freemarker.Theme;
|
||||
|
@ -216,7 +217,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
try {
|
||||
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
|
||||
return Response.status(status).type(MediaType.TEXT_HTML).entity(result).build();
|
||||
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
|
||||
BrowserSecurityHeaderSetup.headers(builder, realm);
|
||||
return builder.build();
|
||||
} catch (FreeMarkerException e) {
|
||||
logger.error("Failed to process template", e);
|
||||
return Response.serverError().build();
|
||||
|
|
27
model/api/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
Executable file
27
model/api/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
Executable file
|
@ -0,0 +1,27 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class BrowserSecurityHeaders {
|
||||
public static final Map<String, String> headerAttributeMap;
|
||||
public static final Map<String, String> defaultHeaders;
|
||||
|
||||
static {
|
||||
Map<String, String> headerMap = new HashMap<String, String>();
|
||||
headerMap.put("xFrameOptions", "X-Frame-Options");
|
||||
headerMap.put("contentSecurityPolicy", "Content-Security-Policy");
|
||||
|
||||
Map<String, String> dh = new HashMap<String, String>();
|
||||
dh.put("xFrameOptions", "SAMEORIGIN");
|
||||
dh.put("contentSecurityPolicy", "frame-src 'self'");
|
||||
|
||||
defaultHeaders = Collections.unmodifiableMap(dh);
|
||||
headerAttributeMap = Collections.unmodifiableMap(headerMap);
|
||||
}
|
||||
}
|
|
@ -151,6 +151,9 @@ public interface RealmModel extends RoleContainerModel {
|
|||
|
||||
List<OAuthClientModel> getOAuthClients();
|
||||
|
||||
Map<String, String> getBrowserSecurityHeaders();
|
||||
void setBrowserSecurityHeaders(Map<String, String> headers);
|
||||
|
||||
Map<String, String> getSmtpConfig();
|
||||
|
||||
void setSmtpConfig(Map<String, String> smtpConfig);
|
||||
|
|
|
@ -52,6 +52,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
|
|||
private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
|
||||
private List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
|
||||
|
||||
private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
|
||||
private Map<String, String> smtpConfig = new HashMap<String, String>();
|
||||
private Map<String, String> socialConfig = new HashMap<String, String>();
|
||||
|
||||
|
@ -317,6 +318,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
|
|||
this.requiredCredentials = requiredCredentials;
|
||||
}
|
||||
|
||||
public Map<String, String> getBrowserSecurityHeaders() {
|
||||
return browserSecurityHeaders;
|
||||
}
|
||||
|
||||
public void setBrowserSecurityHeaders(Map<String, String> browserSecurityHeaders) {
|
||||
this.browserSecurityHeaders = browserSecurityHeaders;
|
||||
}
|
||||
|
||||
public Map<String, String> getSmtpConfig() {
|
||||
return smtpConfig;
|
||||
}
|
||||
|
|
|
@ -105,6 +105,7 @@ public class ModelToRepresentation {
|
|||
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
|
||||
rep.setSmtpServer(realm.getSmtpConfig());
|
||||
rep.setSocialProviders(realm.getSocialConfig());
|
||||
rep.setBrowserSecurityHeaders(realm.getBrowserSecurityHeaders());
|
||||
rep.setAccountTheme(realm.getAccountTheme());
|
||||
rep.setLoginTheme(realm.getLoginTheme());
|
||||
rep.setAdminTheme(realm.getAdminTheme());
|
||||
|
|
|
@ -4,6 +4,7 @@ import net.iharder.Base64;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.enums.SslRequired;
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
import org.keycloak.models.BrowserSecurityHeaders;
|
||||
import org.keycloak.models.ClaimMask;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -199,6 +200,12 @@ public class RepresentationToModel {
|
|||
newRealm.setSmtpConfig(new HashMap(rep.getSmtpServer()));
|
||||
}
|
||||
|
||||
if (rep.getBrowserSecurityHeaders() != null) {
|
||||
newRealm.setBrowserSecurityHeaders(rep.getBrowserSecurityHeaders());
|
||||
} else {
|
||||
newRealm.setBrowserSecurityHeaders(BrowserSecurityHeaders.defaultHeaders);
|
||||
}
|
||||
|
||||
if (rep.getSocialProviders() != null) {
|
||||
newRealm.setSocialConfig(new HashMap(rep.getSocialProviders()));
|
||||
}
|
||||
|
@ -266,6 +273,10 @@ public class RepresentationToModel {
|
|||
realm.setSocialConfig(new HashMap(rep.getSocialProviders()));
|
||||
}
|
||||
|
||||
if (rep.getBrowserSecurityHeaders() != null) {
|
||||
realm.setBrowserSecurityHeaders(rep.getBrowserSecurityHeaders());
|
||||
}
|
||||
|
||||
if (rep.getUserFederationProviders() != null) {
|
||||
List<UserFederationProviderModel> providerModels = convertFederationProviders(rep.getUserFederationProviders());
|
||||
realm.setUserFederationProviders(providerModels);
|
||||
|
|
|
@ -557,6 +557,19 @@ public class RealmAdapter implements RealmModel {
|
|||
return clients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getBrowserSecurityHeaders() {
|
||||
if (updated != null) return updated.getBrowserSecurityHeaders();
|
||||
return cached.getBrowserSecurityHeaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBrowserSecurityHeaders(Map<String, String> headers) {
|
||||
getDelegateForUpdate();
|
||||
updated.setBrowserSecurityHeaders(headers);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getSmtpConfig() {
|
||||
if (updated != null) return updated.getSmtpConfig();
|
||||
|
|
|
@ -66,6 +66,7 @@ public class CachedRealm {
|
|||
private List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
|
||||
private List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
|
||||
|
||||
private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
|
||||
private Map<String, String> smtpConfig = new HashMap<String, String>();
|
||||
private Map<String, String> socialConfig = new HashMap<String, String>();
|
||||
|
||||
|
@ -123,6 +124,7 @@ public class CachedRealm {
|
|||
|
||||
smtpConfig.putAll(model.getSmtpConfig());
|
||||
socialConfig.putAll(model.getSocialConfig());
|
||||
browserSecurityHeaders.putAll(model.getBrowserSecurityHeaders());
|
||||
|
||||
auditEnabled = model.isAuditEnabled();
|
||||
auditExpiration = model.getAuditExpiration();
|
||||
|
@ -287,6 +289,10 @@ public class CachedRealm {
|
|||
return socialConfig;
|
||||
}
|
||||
|
||||
public Map<String, String> getBrowserSecurityHeaders() {
|
||||
return browserSecurityHeaders;
|
||||
}
|
||||
|
||||
public String getLoginTheme() {
|
||||
return loginTheme;
|
||||
}
|
||||
|
|
|
@ -196,6 +196,7 @@ public class RealmAdapter implements RealmModel {
|
|||
}
|
||||
|
||||
public Map<String, String> getAttributes() {
|
||||
// should always return a copy
|
||||
Map<String, String> result = new HashMap<String, String>();
|
||||
for (RealmAttributeEntity attr : realm.getAttributes()) {
|
||||
result.put(attr.getName(), attr.getValue());
|
||||
|
@ -711,6 +712,27 @@ public class RealmAdapter implements RealmModel {
|
|||
return list;
|
||||
}
|
||||
|
||||
private static final String BROWSER_HEADER_PREFIX = "_browser_header.";
|
||||
|
||||
@Override
|
||||
public Map<String, String> getBrowserSecurityHeaders() {
|
||||
Map<String, String> attributes = getAttributes();
|
||||
Map<String, String> headers = new HashMap<String, String>();
|
||||
for (Map.Entry<String, String> entry : attributes.entrySet()) {
|
||||
if (entry.getKey().startsWith(BROWSER_HEADER_PREFIX)) {
|
||||
headers.put(entry.getKey().substring(BROWSER_HEADER_PREFIX.length()), entry.getValue());
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBrowserSecurityHeaders(Map<String, String> headers) {
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
setAttribute(BROWSER_HEADER_PREFIX + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getSmtpConfig() {
|
||||
return realm.getSmtpConfig();
|
||||
|
|
|
@ -734,6 +734,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getBrowserSecurityHeaders() {
|
||||
return realm.getBrowserSecurityHeaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBrowserSecurityHeaders(Map<String, String> headers) {
|
||||
realm.setBrowserSecurityHeaders(headers);
|
||||
updateRealm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getSmtpConfig() {
|
||||
return realm.getSmtpConfig();
|
||||
|
|
|
@ -7,6 +7,7 @@ import org.keycloak.exportimport.util.ImportUtils;
|
|||
import org.keycloak.models.AccountRoles;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
import org.keycloak.models.BrowserSecurityHeaders;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -117,6 +118,8 @@ public class RealmManager {
|
|||
|
||||
|
||||
protected void setupRealmDefaults(RealmModel realm) {
|
||||
realm.setBrowserSecurityHeaders(BrowserSecurityHeaders.defaultHeaders);
|
||||
|
||||
// brute force
|
||||
realm.setBruteForceProtected(false); // default settings off for now todo set it on
|
||||
realm.setMaxFailureWaitSeconds(900);
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.jboss.resteasy.spi.HttpResponse;
|
|||
import org.jboss.resteasy.spi.NotFoundException;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
||||
import org.keycloak.freemarker.Theme;
|
||||
import org.keycloak.freemarker.ThemeProvider;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
|
@ -325,7 +326,9 @@ public class AdminConsole {
|
|||
cacheControl.setNoTransform(false);
|
||||
cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1));
|
||||
|
||||
return Response.ok(resource).type(contentType).cacheControl(cacheControl).build();
|
||||
Response.ResponseBuilder builder = Response.ok(resource).type(contentType).cacheControl(cacheControl);
|
||||
BrowserSecurityHeaderSetup.headers(builder, realm);
|
||||
return builder.build();
|
||||
} else {
|
||||
return Response.status(Response.Status.NOT_FOUND).build();
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.junit.After;
|
|||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.audit.Details;
|
||||
|
@ -158,12 +159,12 @@ public class AccountTest {
|
|||
});
|
||||
}
|
||||
|
||||
/*
|
||||
@Ignore
|
||||
@Test
|
||||
public void forever() throws Exception{
|
||||
while (true) Thread.sleep(5000);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
@Test
|
||||
public void returnToAppFromQueryParam() {
|
||||
|
|
|
@ -278,6 +278,9 @@ public class AdminAPITest {
|
|||
if (rep.getSocialProviders() != null) {
|
||||
Assert.assertEquals(rep.getSocialProviders(), storedRealm.getSocialProviders());
|
||||
}
|
||||
if (rep.getBrowserSecurityHeaders() != null) {
|
||||
Assert.assertEquals(rep.getBrowserSecurityHeaders(), storedRealm.getBrowserSecurityHeaders());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.models.BrowserSecurityHeaders;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -42,6 +43,11 @@ import org.keycloak.testsuite.rule.WebResource;
|
|||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
|
@ -85,6 +91,20 @@ public class LoginTest {
|
|||
|
||||
private static String userId;
|
||||
|
||||
@Test
|
||||
public void testBrowserSecurityHeaders() {
|
||||
Client client = ClientBuilder.newClient();
|
||||
Response response = client.target(oauth.getLoginFormUrl()).request().get();
|
||||
Assert.assertEquals(200, response.getStatus());
|
||||
for (Map.Entry<String, String> entry : BrowserSecurityHeaders.defaultHeaders.entrySet()) {
|
||||
String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
|
||||
String headerValue = response.getHeaderString(headerName);
|
||||
Assert.assertNotNull(headerValue);
|
||||
Assert.assertEquals(headerValue, entry.getValue());
|
||||
}
|
||||
response.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginInvalidPassword() {
|
||||
loginPage.open();
|
||||
|
|
Loading…
Reference in a new issue