Skip to content
This repository was archived by the owner on Dec 12, 2018. It is now read-only.

Commit 3d8e3a8

Browse files
committed
Adding servlet config support for Okta example
1 parent b9c0c15 commit 3d8e3a8

File tree

13 files changed

+244
-63
lines changed

13 files changed

+244
-63
lines changed

api/src/main/java/com/stormpath/sdk/client/ClientBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ public interface ClientBuilder {
222222
String DEFAULT_CLIENT_PROXY_HOST_PROPERTY_NAME = "stormpath.client.proxy.host";
223223
String DEFAULT_CLIENT_PROXY_USERNAME_PROPERTY_NAME = "stormpath.client.proxy.username";
224224
String DEFAULT_CLIENT_PROXY_PASSWORD_PROPERTY_NAME = "stormpath.client.proxy.password";
225+
String DEFAULT_OKTA_ENABLED_PROPERTY_NAME = "okta.enabled";
225226

226227
/**
227228
* Allows specifying an {@code ApiKey} instance directly instead of relying on the

examples/servlet/src/main/webapp/WEB-INF/stormpath.properties

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,17 @@ stormpath.web.login.preHandler=com.stormpath.sdk.examples.servlet.PreLoginHandle
187187
stormpath.web.login.postHandler=com.stormpath.sdk.examples.servlet.PostLoginHandler
188188

189189
stormpath.web.register.preHandler=com.stormpath.sdk.examples.servlet.PreRegisterHandler
190-
stormpath.web.register.postHandler=com.stormpath.sdk.examples.servlet.PostRegisterHandler
190+
stormpath.web.register.postHandler=com.stormpath.sdk.examples.servlet.PostRegisterHandler
191+
192+
#######################################################
193+
# Okta required fields for servlet based applications #
194+
#######################################################
195+
196+
stormpath.client.authenticationScheme=SSWS
197+
okta.enabled=true
198+
stormpath.web.account.jwt.resolver = com.stormpath.sdk.servlet.filter.account.config.OktaJwtAccountResolverFactory
199+
stormpath.web.account.jwt.signingKey.resolver = com.stormpath.sdk.servlet.application.okta.OktaJwtSigningKeyResolver
200+
201+
# okta.api.token=
202+
# okta.application.id=
203+
# stormpath.client.baseUrl=

extensions/servlet/src/main/java/com/stormpath/sdk/servlet/application/DefaultApplicationResolver.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
import com.stormpath.sdk.application.Application;
1919
import com.stormpath.sdk.application.ApplicationList;
2020
import com.stormpath.sdk.client.Client;
21+
import com.stormpath.sdk.impl.application.okta.OktaApplication;
22+
import com.stormpath.sdk.impl.ds.InternalDataStore;
2123
import com.stormpath.sdk.lang.Assert;
24+
import com.stormpath.sdk.lang.Strings;
2225
import com.stormpath.sdk.servlet.client.DefaultServletContextClientFactory;
2326
import com.stormpath.sdk.servlet.config.Config;
2427

@@ -82,12 +85,16 @@ public Application getApplication(final ServletContext servletContext) {
8285
//get the client:
8386
Client client = getClient(servletContext);
8487

88+
Config config = getConfig(servletContext);
89+
if (Boolean.valueOf(config.get("okta.enabled"))) {
90+
return new OktaApplication((InternalDataStore) client.getDataStore());
91+
}
92+
8593
//this is a local cached href value that we use in case we have to query applications (see below):
8694
String href = (String) servletContext.getAttribute(STORMPATH_APPLICATION_HREF);
8795

8896
if (href == null) {
8997
//no cached value = try config:
90-
Config config = getConfig(servletContext);
9198
href = config.get(STORMPATH_APPLICATION_HREF);
9299
}
93100

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.stormpath.sdk.servlet.application.okta;
2+
3+
import com.stormpath.sdk.authc.AuthenticationResult;
4+
import com.stormpath.sdk.ds.DataStore;
5+
import com.stormpath.sdk.impl.application.okta.OktaSigningKeyResolver;
6+
import com.stormpath.sdk.servlet.filter.account.JwtSigningKeyResolver;
7+
import io.jsonwebtoken.Claims;
8+
import io.jsonwebtoken.JwsHeader;
9+
import io.jsonwebtoken.SignatureAlgorithm;
10+
11+
import javax.servlet.http.HttpServletRequest;
12+
import javax.servlet.http.HttpServletResponse;
13+
import java.security.Key;
14+
15+
/**
16+
*
17+
*/
18+
public class OktaJwtSigningKeyResolver implements JwtSigningKeyResolver {
19+
20+
private DataStore dataStore;
21+
22+
public OktaJwtSigningKeyResolver() {
23+
24+
}
25+
26+
public OktaJwtSigningKeyResolver(DataStore dataStore)
27+
{
28+
this.dataStore = dataStore;
29+
}
30+
31+
public void setDataStore(DataStore dataStore) {
32+
this.dataStore = dataStore;
33+
}
34+
35+
@Override
36+
public Key getSigningKey(HttpServletRequest request, HttpServletResponse response, AuthenticationResult result, SignatureAlgorithm alg) {
37+
throw new UnsupportedOperationException("getSigningKey() is not supported");
38+
}
39+
40+
@Override
41+
public Key getSigningKey(HttpServletRequest request, HttpServletResponse response, JwsHeader jwsHeader, Claims claims) {
42+
return dataStore.instantiate(OktaSigningKeyResolver.class).resolveSigningKey(jwsHeader, claims);
43+
}
44+
}

extensions/servlet/src/main/java/com/stormpath/sdk/servlet/client/DefaultServletContextClientFactory.java

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,21 @@
1818
import com.stormpath.sdk.api.ApiKey;
1919
import com.stormpath.sdk.api.ApiKeyBuilder;
2020
import com.stormpath.sdk.api.ApiKeys;
21+
import com.stormpath.sdk.application.okta.ApplicationCredentials;
2122
import com.stormpath.sdk.cache.CacheManager;
2223
import com.stormpath.sdk.client.AuthenticationScheme;
2324
import com.stormpath.sdk.client.Client;
2425
import com.stormpath.sdk.client.ClientBuilder;
2526
import com.stormpath.sdk.client.Clients;
27+
import com.stormpath.sdk.client.DefaultPairedApiKey;
28+
import com.stormpath.sdk.client.PairedApiKey;
2629
import com.stormpath.sdk.client.Proxy;
30+
import com.stormpath.sdk.impl.api.ClientApiKey;
31+
import com.stormpath.sdk.impl.application.okta.DefaultOktaSigningKeyResolver;
2732
import com.stormpath.sdk.impl.cache.DisabledCacheManager;
2833
import com.stormpath.sdk.lang.Assert;
2934
import com.stormpath.sdk.lang.Strings;
35+
import com.stormpath.sdk.servlet.application.okta.OktaJwtSigningKeyResolver;
3036
import com.stormpath.sdk.servlet.config.Config;
3137
import com.stormpath.sdk.servlet.config.ConfigResolver;
3238

@@ -86,7 +92,8 @@ public Client createClient(ServletContext servletContext) {
8692

8793
applyCacheManager(builder);
8894

89-
return builder.build();
95+
return postConfigure(builder.build());
96+
9097
}
9198

9299
protected void applyBaseUrl(ClientBuilder builder) {
@@ -121,25 +128,33 @@ protected void applyApiKey(ClientBuilder clientBuilder) {
121128

122129
protected ApiKey createApiKey() {
123130

124-
ApiKeyBuilder apiKeyBuilder = ApiKeys.builder();
125-
126-
String value = config.get("stormpath.client.apiKey.id");
127-
if (Strings.hasText(value)) {
128-
apiKeyBuilder.setId(value);
131+
if (isOktaEnabled()) {
132+
String oktaToken = config.get("okta.api.token");
133+
Assert.hasText(oktaToken, "'okta.api.token' is required when 'okta.enable' is true"); // TODO: duplicate code from spring config
134+
return new DefaultPairedApiKey(new ClientApiKey("okta_api_token", oktaToken));
129135
}
136+
else {
130137

131-
//check for API Key ID embedded in the properties configuration
132-
value = config.get("stormpath.client.apiKey.secret");
133-
if (Strings.hasText(value)) {
134-
apiKeyBuilder.setSecret(value);
135-
}
138+
ApiKeyBuilder apiKeyBuilder = ApiKeys.builder();
136139

137-
value = config.get(STORMPATH_API_KEY_FILE);
138-
if (Strings.hasText(value)) {
139-
apiKeyBuilder.setFileLocation(value);
140-
}
140+
String value = config.get("stormpath.client.apiKey.id");
141+
if (Strings.hasText(value)) {
142+
apiKeyBuilder.setId(value);
143+
}
144+
145+
//check for API Key ID embedded in the properties configuration
146+
value = config.get("stormpath.client.apiKey.secret");
147+
if (Strings.hasText(value)) {
148+
apiKeyBuilder.setSecret(value);
149+
}
150+
151+
value = config.get(STORMPATH_API_KEY_FILE);
152+
if (Strings.hasText(value)) {
153+
apiKeyBuilder.setFileLocation(value);
154+
}
141155

142-
return apiKeyBuilder.build();
156+
return apiKeyBuilder.build();
157+
}
143158
}
144159

145160
protected void applyProxy(ClientBuilder builder) {
@@ -178,4 +193,45 @@ protected void applyAuthenticationScheme(ClientBuilder builder) {
178193
builder.setAuthenticationScheme(scheme);
179194
}
180195
}
196+
197+
protected Client postConfigure(Client client) {
198+
199+
if (isOktaEnabled()) {
200+
String oktaApplicationId = config.get("okta.application.id");
201+
202+
Assert.hasText(oktaApplicationId, "When okta.enabled is true, okta.application.id " +
203+
"must be configured with your Okta Application ID. This can be found in the URL when accessing " +
204+
"you application in a browser.");
205+
206+
String applicationCredentialsHref = "/api/v1/internal/apps/" + oktaApplicationId + "/settings/clientcreds";
207+
ApplicationCredentials applicationCredentials = client.getResource(applicationCredentialsHref, ApplicationCredentials.class);
208+
209+
ApiKey secondary = ApiKeys.builder()
210+
.setId(applicationCredentials.getClientId())
211+
.setSecret(applicationCredentials.getClientSecret())
212+
.build();
213+
214+
((PairedApiKey)client.getApiKey()).setSecondaryApiKey(secondary);
215+
216+
217+
try {
218+
OktaJwtSigningKeyResolver keyResolver = config.getInstance("stormpath.web.account.jwt.signingKey.resolver");
219+
keyResolver.setDataStore(client.getDataStore());
220+
} catch (ServletException e) {
221+
throw new UnsupportedOperationException("Expected 'stormpath.web.account.jwt.signingKey.resolver', to be of type 'DefaultOktaSigningKeyResolver'", e); // TODO: user interface if this works
222+
}
223+
224+
}
225+
return client;
226+
}
227+
228+
private boolean isOktaEnabled() {
229+
230+
boolean oktaEnabled = false;
231+
String oktaEnabledString = config.get("okta.enabled");
232+
if (Strings.hasText(oktaEnabledString)) {
233+
oktaEnabled = Boolean.valueOf(oktaEnabledString);
234+
}
235+
return oktaEnabled;
236+
}
181237
}

extensions/servlet/src/main/java/com/stormpath/sdk/servlet/config/impl/DefaultConfigFactory.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ public String[] map(String key, String value) {
107107
key = envVarNameConverter.toDottedPropertyName(key);
108108
return new String[]{key, value};
109109
}
110+
else if (key.startsWith("OKTA_")) {
111+
//we want to convert env var naming convention to dotted property convention
112+
//to allow overrides. Overrides work based on overriding identically-named keys:
113+
key = envVarNameConverter.toDottedPropertyName(key);
114+
return new String[]{key, value};
115+
}
110116
return null;
111117
}
112118
}));
@@ -119,6 +125,9 @@ public String[] map(String key, String value) {
119125
if (key.startsWith("stormpath.")) {
120126
return new String[]{key, value};
121127
}
128+
else if (key.startsWith("okta.")) {
129+
return new String[]{key, value};
130+
}
122131
return null;
123132
}
124133
}));

extensions/servlet/src/main/java/com/stormpath/sdk/servlet/filter/account/OktaJwtAccountResolver.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,41 @@
55
import com.stormpath.sdk.impl.okta.OktaApiPaths;
66
import io.jsonwebtoken.Claims;
77
import io.jsonwebtoken.Jws;
8+
import io.jsonwebtoken.JwsHeader;
89
import io.jsonwebtoken.Jwts;
910
import io.jsonwebtoken.SigningKeyResolver;
1011

1112
import javax.servlet.http.HttpServletRequest;
1213
import javax.servlet.http.HttpServletResponse;
14+
import java.security.Key;
1315

1416
/**
1517
*
1618
*/
1719
public class OktaJwtAccountResolver implements JwtAccountResolver {
1820

19-
private final SigningKeyResolver signingKeyResolver;
21+
private final JwtSigningKeyResolver signingKeyResolver;
2022

21-
public OktaJwtAccountResolver(SigningKeyResolver signingKeyResolver) {
23+
public OktaJwtAccountResolver(JwtSigningKeyResolver signingKeyResolver) {
2224
this.signingKeyResolver = signingKeyResolver;
2325
}
2426

2527
@Override
26-
public Account getAccountByJwt(HttpServletRequest request, HttpServletResponse response, String jwt) {
28+
public Account getAccountByJwt(final HttpServletRequest request, final HttpServletResponse response, String jwt) {
2729

2830
Client client = getClient(request);
2931

30-
Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(jwt);
32+
Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolver() {
33+
@Override
34+
public Key resolveSigningKey(JwsHeader header, Claims claims) {
35+
return signingKeyResolver.getSigningKey(request, response, header, claims);
36+
}
37+
38+
@Override
39+
public Key resolveSigningKey(JwsHeader header, String plaintext) {
40+
throw new UnsupportedOperationException("resolveSigningKey(JwsHeader header, String plaintext), has not been implemented");
41+
}
42+
}).parseClaimsJws(jwt);
3143
Claims claims = jws.getBody();
3244

3345
// TODO" not sure if this is needed

extensions/servlet/src/main/java/com/stormpath/sdk/servlet/filter/account/config/CookieAuthenticationResultSaverFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ protected Saver<AuthenticationResult> createInstance(ServletContext servletConte
4040
CookieConfig accessTokenCookieConfig = config.getAccessTokenCookieConfig();
4141
CookieConfig refreshTokenCookieConfig = config.getRefreshTokenCookieConfig();
4242
Resolver<Boolean> secureCookieRequired = config.getInstance(COOKIE_SECURE_RESOLVER);
43-
JwtSigningKeyResolver signingKeyResolver = config.getInstance(JwtSigningKeyResolver.class.getName()); // TODO: does this work?
43+
JwtSigningKeyResolver signingKeyResolver = config.getInstance("stormpath.web.account.jwt.signingKey.resolver");
4444
return new CookieAuthenticationResultSaver(
4545
accessTokenCookieConfig,
4646
refreshTokenCookieConfig,

extensions/servlet/src/main/java/com/stormpath/sdk/servlet/filter/account/config/JwtAccountResolverFactory.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package com.stormpath.sdk.servlet.filter.account.config;
1717

18+
import com.stormpath.sdk.servlet.config.Config;
19+
import com.stormpath.sdk.servlet.config.ConfigResolver;
1820
import com.stormpath.sdk.servlet.config.ConfigSingletonFactory;
1921
import com.stormpath.sdk.servlet.filter.account.DefaultJwtAccountResolver;
2022
import com.stormpath.sdk.servlet.filter.account.JwtAccountResolver;
@@ -30,7 +32,8 @@ public class JwtAccountResolverFactory extends ConfigSingletonFactory<JwtAccount
3032

3133
@Override
3234
protected JwtAccountResolver createInstance(ServletContext servletContext) throws Exception {
33-
JwtSigningKeyResolver resolver = new JwtTokenSigningKeyResolver();
35+
Config config = ConfigResolver.INSTANCE.getConfig(servletContext);
36+
JwtSigningKeyResolver resolver = config.getInstance("stormpath.web.account.jwt.signingKey.resolver");
3437
return new DefaultJwtAccountResolver(resolver);
3538
}
3639
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2015 Stormpath, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.stormpath.sdk.servlet.filter.account.config;
17+
18+
import com.stormpath.sdk.servlet.config.Config;
19+
import com.stormpath.sdk.servlet.config.ConfigResolver;
20+
import com.stormpath.sdk.servlet.config.ConfigSingletonFactory;
21+
import com.stormpath.sdk.servlet.filter.account.DefaultJwtAccountResolver;
22+
import com.stormpath.sdk.servlet.filter.account.JwtAccountResolver;
23+
import com.stormpath.sdk.servlet.filter.account.JwtSigningKeyResolver;
24+
import com.stormpath.sdk.servlet.filter.account.OktaJwtAccountResolver;
25+
26+
import javax.servlet.ServletContext;
27+
28+
/**
29+
*
30+
*/
31+
public class OktaJwtAccountResolverFactory extends ConfigSingletonFactory<JwtAccountResolver> {
32+
33+
@Override
34+
protected JwtAccountResolver createInstance(ServletContext servletContext) throws Exception {
35+
Config config = ConfigResolver.INSTANCE.getConfig(servletContext);
36+
JwtSigningKeyResolver resolver = config.getInstance("stormpath.web.account.jwt.signingKey.resolver");
37+
38+
return new OktaJwtAccountResolver(resolver);
39+
}
40+
}

0 commit comments

Comments
 (0)