/
OidcCookieTokenStore.java
239 lines (212 loc) · 10.2 KB
/
OidcCookieTokenStore.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/*
* JBoss, Home of Professional Open Source.
* Copyright 2021 Red Hat, Inc., and individual 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.wildfly.security.http.oidc;
import static org.wildfly.security.http.oidc.ElytronMessages.log;
import static org.wildfly.security.http.oidc.Oidc.OIDC_STATE_COOKIE;
import java.net.URISyntaxException;
import java.util.List;
import org.apache.http.client.utils.URIBuilder;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.Scope;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class OidcCookieTokenStore implements OidcTokenStore {
private final OidcHttpFacade httpFacade;
private static final String DELIM = "###";
private static final String LEGACY_DELIM = "___";
private static final int EXPECTED_NUM_TOKENS = 3;
private static final int ACCESS_TOKEN_INDEX = 0;
private static final int ID_TOKEN_INDEX = 1;
private static final int REFRESH_TOKEN_INDEX = 2;
public OidcCookieTokenStore(OidcHttpFacade httpFacade) {
this.httpFacade = httpFacade;
}
@Override
public void checkCurrentToken() {
OidcClientConfiguration deployment = httpFacade.getOidcClientConfiguration();
OidcPrincipal<RefreshableOidcSecurityContext> principal = OidcCookieTokenStore.getPrincipalFromCookie(deployment, httpFacade, this);
if (principal == null) {
return;
}
RefreshableOidcSecurityContext securityContext = principal.getOidcSecurityContext();
if (securityContext.isActive() && ! securityContext.getOidcClientConfiguration().isAlwaysRefreshToken()) return;
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
// not be updated
boolean success = securityContext.refreshToken(false);
if (success && securityContext.isActive()) return;
saveAccountInfo(new OidcAccount(principal));
}
@Override
public boolean isCached(RequestAuthenticator authenticator) {
OidcClientConfiguration deployment = httpFacade.getOidcClientConfiguration();
OidcPrincipal<RefreshableOidcSecurityContext> principal = OidcCookieTokenStore.getPrincipalFromCookie(deployment, httpFacade, this);
if (principal == null) {
log.debug("Account was not in cookie or was invalid, returning null");
return false;
}
OidcAccount account = new OidcAccount(principal);
if (deployment.getRealm() != null && ! deployment.getRealm().equals(account.getOidcSecurityContext().getRealm())) {
log.debug("Account in session belongs to a different realm than for this request.");
return false;
}
boolean active = account.checkActive();
if (! active) {
active = account.tryRefresh();
}
if (active) {
log.debug("Cached account found");
restoreRequest();
httpFacade.authenticationComplete(account, true);
return true;
} else {
log.debug("Account was not active, removing cookie and returning false");
removeCookie(deployment, httpFacade);
return false;
}
}
@Override
public void saveAccountInfo(OidcAccount account) {
RefreshableOidcSecurityContext secContext = account.getOidcSecurityContext();
OidcCookieTokenStore.setTokenCookie(this.httpFacade.getOidcClientConfiguration(), this.httpFacade, secContext);
HttpScope exchange = this.httpFacade.getScope(Scope.EXCHANGE);
exchange.registerForNotification(httpServerScopes -> logout());
exchange.setAttachment(OidcAccount.class.getName(), account);
exchange.setAttachment(OidcSecurityContext.class.getName(), account.getOidcSecurityContext());
restoreRequest();
}
@Override
public void logout() {
logout(false);
}
@Override
public void refreshCallback(RefreshableOidcSecurityContext securityContext) {
OidcCookieTokenStore.setTokenCookie(this.httpFacade.getOidcClientConfiguration(), httpFacade, securityContext);
}
@Override
public void saveRequest() {
}
@Override
public boolean restoreRequest() {
return false;
}
@Override
public void logout(boolean glo) {
OidcPrincipal<RefreshableOidcSecurityContext> principal = OidcCookieTokenStore.getPrincipalFromCookie(httpFacade.getOidcClientConfiguration(), httpFacade, this);
if (principal == null) {
return;
}
OidcCookieTokenStore.removeCookie(httpFacade.getOidcClientConfiguration(), httpFacade);
if (glo) {
OidcSecurityContext securityContext = principal.getOidcSecurityContext();
if (securityContext == null) {
return;
}
OidcClientConfiguration deployment = httpFacade.getOidcClientConfiguration();
if (! deployment.isBearerOnly() && securityContext != null && securityContext instanceof RefreshableOidcSecurityContext) {
((RefreshableOidcSecurityContext) securityContext).logout(deployment);
}
}
}
@Override
public void logoutAll() {
//no-op
}
@Override
public void logoutHttpSessions(List<String> ids) {
//no-op
}
public static void removeCookie(OidcClientConfiguration deployment, OidcHttpFacade facade) {
String cookiePath = getCookiePath(deployment, facade);
facade.getResponse().resetCookie(OIDC_STATE_COOKIE, cookiePath);
}
public static void setTokenCookie(OidcClientConfiguration deployment, OidcHttpFacade facade, RefreshableOidcSecurityContext session) {
log.debugf("Set new %s cookie now", OIDC_STATE_COOKIE);
String accessToken = session.getTokenString();
String idToken = session.getIDTokenString();
String refreshToken = session.getRefreshToken();
String cookie = new StringBuilder(accessToken).append(DELIM)
.append(idToken).append(DELIM)
.append(refreshToken).toString();
String cookiePath = getCookiePath(deployment, facade);
facade.getResponse().setCookie(OIDC_STATE_COOKIE, cookie, cookiePath, null, -1, deployment.getSSLRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
}
static String getCookiePath(OidcClientConfiguration deployment, OidcHttpFacade facade) {
String path = deployment.getOidcStateCookiePath() == null ? "" : deployment.getOidcStateCookiePath().trim();
if (path.startsWith("/")) {
return path;
}
String contextPath = getContextPath(facade);
StringBuilder cookiePath = new StringBuilder(contextPath);
if (!contextPath.endsWith("/") && !path.isEmpty()) {
cookiePath.append("/");
}
return cookiePath.append(path).toString();
}
static String getContextPath(OidcHttpFacade facade) {
String uri = facade.getRequest().getURI();
String path = null;
try {
new URIBuilder(uri).build().getPath();
} catch (URISyntaxException e) {
throw log.invalidUri(uri);
}
if (path == null || path.isEmpty()) {
return "/";
}
int index = path.indexOf("/", 1);
return index == -1 ? path : path.substring(0, index);
}
public static OidcPrincipal<RefreshableOidcSecurityContext> getPrincipalFromCookie(OidcClientConfiguration deployment, OidcHttpFacade facade, OidcCookieTokenStore tokenStore) {
OidcHttpFacade.Cookie cookie = facade.getRequest().getCookie(OIDC_STATE_COOKIE);
if (cookie == null) {
log.debug("OIDC state cookie not found in current request");
return null;
}
String cookieVal = cookie.getValue();
String[] tokens = cookieVal.split(DELIM);
if (tokens.length != EXPECTED_NUM_TOKENS) {
// Cookies set by older versions of wildfly-elytron use a different token delimiter. Since clients may
// still send such cookies we fall back to the old delimiter to avoid discarding valid tokens:
tokens = cookieVal.split(LEGACY_DELIM);
}
if (tokens.length != EXPECTED_NUM_TOKENS) {
log.warnf("Invalid format of %s cookie. Count of tokens: %s, expected %s", OIDC_STATE_COOKIE, tokens.length, EXPECTED_NUM_TOKENS);
log.debugf("Value of %s cookie is: %s", OIDC_STATE_COOKIE, cookieVal);
return null;
}
String accessTokenString = tokens[ACCESS_TOKEN_INDEX];
String idTokenString = tokens[ID_TOKEN_INDEX];
String refreshTokenString = tokens[REFRESH_TOKEN_INDEX];
try {
AccessToken accessToken = new AccessToken(new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(accessTokenString));
IDToken idToken = null;
if (idTokenString != null && idTokenString.length() > 0) {
idToken = new IDToken(new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(idTokenString));
}
log.debug("Token obtained from cookie");
RefreshableOidcSecurityContext secContext = new RefreshableOidcSecurityContext(deployment, tokenStore, accessTokenString, accessToken, idTokenString, idToken, refreshTokenString);
return new OidcPrincipal<>(idToken.getPrincipalName(deployment), secContext);
} catch (InvalidJwtException e) {
log.failedToParseTokenFromCookie(e);
return null;
}
}
}