Skip to content

Commit

Permalink
Merge pull request #39 from oktadeveloper/post-logout
Browse files Browse the repository at this point in the history
Support postLogout URI and native application reverse DNS filter property
  • Loading branch information
bdemers authored Sep 28, 2020
2 parents cb70c7b + fdea4be commit f3f464d
Show file tree
Hide file tree
Showing 22 changed files with 460 additions and 76 deletions.
2 changes: 1 addition & 1 deletion cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<parent>
<groupId>com.okta.cli</groupId>
<artifactId>okta-cli-tools</artifactId>
<version>0.6.1-SNAPSHOT</version>
<version>0.7.0-SNAPSHOT</version>
</parent>

<artifactId>okta-cli</artifactId>
Expand Down
16 changes: 14 additions & 2 deletions cli/src/main/java/com/okta/cli/commands/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
import com.okta.cli.common.model.FilterConfigBuilder;
import com.okta.cli.common.model.OktaSampleConfig;
import com.okta.cli.common.model.SamplesListings;
import com.okta.cli.common.service.ClientConfigurationException;
import com.okta.cli.common.service.DefaultInterpolator;
import com.okta.cli.common.service.DefaultSampleConfigParser;
import com.okta.cli.common.service.DefaultSamplesService;
import com.okta.cli.common.service.DefaultSdkConfigurationService;
import com.okta.cli.common.service.DefaultSetupService;
import com.okta.cli.common.service.TarballExtractor;
import com.okta.cli.console.ConsoleOutput;
Expand Down Expand Up @@ -111,8 +113,13 @@ public Integer call() throws Exception {
extractedProject = true;
}

// TODO need to better abstract away the ~/.okta/okta.yaml config values
Map<String, String> sampleContext = new FilterConfigBuilder().setOrgUrl(oktaBaseUrl()).build();

// parse the `.okta.yaml` file
OktaSampleConfig config = new DefaultSampleConfigParser().loadConfig(projectDirectory);
OktaSampleConfig config = new DefaultSampleConfigParser().loadConfig(projectDirectory, sampleContext);
OpenIdConnectApplicationType applicationType = OpenIdConnectApplicationType.valueOf(
config.getOAuthClient().getApplicationType().toUpperCase(Locale.ENGLISH));

// create the Okta application
Client client = Clients.builder().build();
Expand All @@ -126,8 +133,9 @@ public Integer call() throws Exception {
authorizationServer.getIssuer(),
authorizationServer.getId(),
true,
OpenIdConnectApplicationType.valueOf(config.getOAuthClient().getApplicationType().toUpperCase(Locale.ENGLISH)), // TODO default to SPA
applicationType,
config.getOAuthClient().getRedirectUris(),
config.getOAuthClient().getPostLogoutRedirectUris(),
config.getTrustedOrigins()
);

Expand Down Expand Up @@ -194,4 +202,8 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) {
return FileVisitResult.CONTINUE;
}
}

private String oktaBaseUrl() throws ClientConfigurationException {
return new DefaultSdkConfigurationService().loadUnvalidatedConfiguration().getBaseUrl();
}
}
41 changes: 28 additions & 13 deletions cli/src/main/java/com/okta/cli/commands/apps/AppsCreate.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.okta.cli.commands.apps.templates.ServiceAppTemplate;
import com.okta.cli.commands.apps.templates.SpaAppTemplate;
import com.okta.cli.commands.apps.templates.WebAppTemplate;
import com.okta.cli.common.URIs;
import com.okta.cli.common.config.MapPropertySource;
import com.okta.cli.common.config.MutablePropertySource;
import com.okta.cli.common.model.AuthorizationServer;
Expand All @@ -28,19 +29,18 @@
import com.okta.cli.common.service.DefaultSetupService;
import com.okta.cli.console.ConsoleOutput;
import com.okta.cli.console.Prompter;
import com.okta.commons.lang.Assert;
import com.okta.sdk.client.Client;
import com.okta.sdk.client.Clients;
import com.okta.sdk.resource.application.OpenIdConnectApplicationType;
import picocli.CommandLine;

import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@CommandLine.Command(name = "create",
description = "Create an new Okta app")
Expand Down Expand Up @@ -104,14 +104,14 @@ private int createWebApp(String appName, WebAppTemplate webAppTemplate) throws I
List<String> redirectUris = getRedirectUris(Map.of("Spring Security", "http://localhost:8080/login/oauth2/code/okta",
"JHipster", "http://localhost:8080/login/oauth2/code/oidc"),
appTemplate.getDefaultRedirectUri());

List<String> postLogoutRedirectUris = getPostLogoutRedirectUris(redirectUris);
Client client = Clients.builder().build();
AuthorizationServer issuer = getIssuer(client);
String baseUrl = getBaseUrl();
String groupClaimName = appTemplate.getGroupsClaim();

MutablePropertySource propertySource = appCreationMixin.getPropertySource(appTemplate.getDefaultConfigFileName());
new DefaultSetupService(appTemplate.getSpringPropertyKey()).createOidcApplication(propertySource, appName, baseUrl, groupClaimName, issuer.getIssuer(), issuer.getId(), true, OpenIdConnectApplicationType.WEB, redirectUris);
new DefaultSetupService(appTemplate.getSpringPropertyKey()).createOidcApplication(propertySource, appName, baseUrl, groupClaimName, issuer.getIssuer(), issuer.getId(), true, OpenIdConnectApplicationType.WEB, redirectUris, postLogoutRedirectUris);

out.writeLine("Okta application configuration has been written to: " + propertySource.getName());

Expand All @@ -122,19 +122,16 @@ private Integer createNativeApp(String appName) throws IOException {

ConsoleOutput out = standardOptions.getEnvironment().getConsoleOutput();
String baseUrl = getBaseUrl();

String[] parts = URI.create(baseUrl).getHost().split("\\.");
String reverseDomain = IntStream.rangeClosed(1, parts.length)
.mapToObj(i -> parts[parts.length - i])
.collect(Collectors.joining("."));
String reverseDomain = URIs.reverseDomain(baseUrl);

String defaultRedirectUri = reverseDomain + ":/callback";
List<String> redirectUris = getRedirectUris(Map.of("Reverse Domain name", "com.example:/callback"), defaultRedirectUri);
List<String> redirectUris = getRedirectUris(Map.of("Reverse Domain name", defaultRedirectUri), defaultRedirectUri);
List<String> postLogoutRedirectUris = getPostLogoutRedirectUris(redirectUris);
Client client = Clients.builder().build();
AuthorizationServer issuer = getIssuer(client);

MutablePropertySource propertySource = new MapPropertySource();
new DefaultSetupService(null).createOidcApplication(propertySource, appName, baseUrl, null, issuer.getIssuer(), issuer.getId(), standardOptions.getEnvironment().isInteractive(), OpenIdConnectApplicationType.NATIVE, redirectUris);
new DefaultSetupService(null).createOidcApplication(propertySource, appName, baseUrl, null, issuer.getIssuer(), issuer.getId(), standardOptions.getEnvironment().isInteractive(), OpenIdConnectApplicationType.NATIVE, redirectUris, postLogoutRedirectUris);

out.writeLine("Okta application configuration: ");
propertySource.getProperties().forEach((key, value) -> {
Expand Down Expand Up @@ -171,11 +168,12 @@ private Integer createSpaApp(String appName) throws IOException {

String baseUrl = getBaseUrl();
List<String> redirectUris = getRedirectUris(Map.of("/callback", "http://localhost:8080/callback"), SpaAppTemplate.GENERIC.getDefaultRedirectUri());
List<String> postLogoutRedirectUris = getPostLogoutRedirectUris(redirectUris);
Client client = Clients.builder().build();
AuthorizationServer authorizationServer = getIssuer(client);

MutablePropertySource propertySource = new MapPropertySource();
new DefaultSetupService(null).createOidcApplication(propertySource, appName, baseUrl, null, authorizationServer.getIssuer(), authorizationServer.getId(), standardOptions.getEnvironment().isInteractive(), OpenIdConnectApplicationType.BROWSER, redirectUris);
new DefaultSetupService(null).createOidcApplication(propertySource, appName, baseUrl, null, authorizationServer.getIssuer(), authorizationServer.getId(), standardOptions.getEnvironment().isInteractive(), OpenIdConnectApplicationType.BROWSER, redirectUris, postLogoutRedirectUris);

out.writeLine("Okta application configuration: ");
out.bold("Issuer: ");
Expand Down Expand Up @@ -214,7 +212,24 @@ private List<String> getRedirectUris(Map<String, String> commonExamples, String
redirectUriPrompt.append("Enter your Redirect URI");

String result = prompter.promptIfEmpty(appCreationMixin.redirectUri, redirectUriPrompt.toString(), defaultRedirectUri).trim();
result = result.replaceFirst("^\\[", "");
return split(result);
}

private List<String> getPostLogoutRedirectUris(List<String> redirectUris) {
Prompter prompter = standardOptions.getEnvironment().prompter();

Assert.notEmpty(redirectUris, "Redirect Uris cannot be empty");
String defaultPostLogoutUri = redirectUris.stream()
.findFirst()
.map(URIs::baseUrlOf)
.get();

String result = prompter.promptIfEmpty(appCreationMixin.redirectUri, "Enter your Post Logout Redirect URI", defaultPostLogoutUri).trim();
return split(result);
}

private List<String> split(String input) {
String result = input.replaceFirst("^\\[", "");
result = result.replaceFirst("]$", "");

return Arrays.stream(result.split(","))
Expand Down
2 changes: 1 addition & 1 deletion common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<parent>
<groupId>com.okta.cli</groupId>
<artifactId>okta-cli-tools</artifactId>
<version>0.6.1-SNAPSHOT</version>
<version>0.7.0-SNAPSHOT</version>
</parent>

<artifactId>okta-cli-common</artifactId>
Expand Down
49 changes: 49 additions & 0 deletions common/src/main/java/com/okta/cli/common/URIs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2020-Present Okta, Inc.
*
* 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 com.okta.cli.common;

import java.net.URI;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public final class URIs {

private URIs() {}

public static String reverseDomain(String uri) {
URI parsedUri = URI.create(uri);

// if the parsed URI does NOT have a protocol, assume it's just a host name
if (parsedUri.getScheme() == null) {
return dnsReverse(uri);
}

return dnsReverse(parsedUri.getHost());
}

public static String baseUrlOf(String url) {
return URI.create(url).resolve("/").toString();
}

private static String dnsReverse(String host) {
String[] parts = host.split("\\.");
String reverseDomain = IntStream.rangeClosed(1, parts.length)
.mapToObj(i -> parts[parts.length - i])
.collect(Collectors.joining("."));

return reverseDomain;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class FilterConfigBuilder {

Expand All @@ -29,6 +31,7 @@ public class FilterConfigBuilder {
private static final String CLI_OKTA_ISSUER_ID = "CLI_OKTA_ISSUER_ID";
private static final String CLI_OKTA_CLIENT_ID = "CLI_OKTA_CLIENT_ID";
private static final String CLI_OKTA_CLIENT_SECRET = "CLI_OKTA_CLIENT_SECRET";
private static final String CLI_OKTA_REVERSE_DOMAIN = "CLI_OKTA_REVERSE_DOMAIN";

private final Map<String, String> filterValues = new HashMap<>();

Expand All @@ -48,7 +51,23 @@ public FilterConfigBuilder setIssuerId(String issuerId) {

public FilterConfigBuilder setIssuer(String issuer) {
filterValues.put(CLI_OKTA_ISSUER, issuer);
filterValues.put(CLI_OKTA_ORG_URL, URI.create(issuer).resolve("/").toString());
setOrgUrl(URI.create(issuer).resolve("/").toString());
return this;
}

public FilterConfigBuilder setOrgUrl(String orgUrl) {
filterValues.put(CLI_OKTA_ORG_URL, orgUrl);

String[] hostParts = URI.create(orgUrl).getHost().split("\\.");
String reverseDomain = IntStream.rangeClosed(1, hostParts.length)
.mapToObj(i -> hostParts[hostParts.length - i])
.collect(Collectors.joining("."));
setReverseDomain(reverseDomain);
return this;
}

public FilterConfigBuilder setReverseDomain(String reverseDomain) {
filterValues.put(CLI_OKTA_REVERSE_DOMAIN, reverseDomain);
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public void setOauthClient(OAuthClient oauthClient) {
@Data
public static class OAuthClient {
private List<String> redirectUris;
private String applicationType;
private List<String> postLogoutRedirectUris;
private String applicationType = "browser";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,15 @@
import com.okta.sdk.resource.application.OpenIdConnectApplicationType;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

class DefaultOidcAppCreator implements OidcAppCreator {

@Override
public ExtensibleResource createOidcApp(Client client, String oidcAppName, List<String> redirectUris) {
public ExtensibleResource createOidcApp(Client client, String oidcAppName, List<String> redirectUris, List<String> postLogoutRedirectUris) {

Optional<Application> existingApp = getApplication(client, oidcAppName);

Expand All @@ -53,16 +51,9 @@ public ExtensibleResource createOidcApp(Client client, String oidcAppName, List<
.setGrantTypes(Collections.singletonList(OAuthGrantType.AUTHORIZATION_CODE))
.setApplicationType(OpenIdConnectApplicationType.WEB);

// TODO expose this setting to the user
// TODO the post redirect URI should be exposed in v2 of the SDK
Set<String> postLogoutRedirect = redirectUris.stream()
.map(redirectUri -> {
URI uri = URI.create(redirectUri).resolve("/");
return uri.toString();
})
.collect(Collectors.toSet());
if (!postLogoutRedirect.isEmpty()) {
oauthClient.put("post_logout_redirect_uris", new ArrayList<>(postLogoutRedirect));
List<String> logoutRedirectUris = getOrDefaultWebBasedPostLogoutRedirectUris(postLogoutRedirectUris, redirectUris);
if (!logoutRedirectUris.isEmpty()) {
oauthClient.setPostLogoutRedirectUris(logoutRedirectUris);
}

Application app = client.instantiate(OpenIdConnectApplication.class)
Expand All @@ -80,7 +71,7 @@ public ExtensibleResource createOidcApp(Client client, String oidcAppName, List<
}

@Override
public ExtensibleResource createOidcNativeApp(Client client, String oidcAppName, List<String> redirectUris) {
public ExtensibleResource createOidcNativeApp(Client client, String oidcAppName, List<String> redirectUris, List<String> postLogoutRedirectUris) {

Optional<Application> existingApp = getApplication(client, oidcAppName);

Expand All @@ -101,8 +92,9 @@ public ExtensibleResource createOidcNativeApp(Client client, String oidcAppName,
.setOAuthClient(client.instantiate(ApplicationCredentialsOAuthClient.class)
.setTokenEndpointAuthMethod(OAuthEndpointAuthenticationMethod.NONE)));

// TODO expose post_logout_redirect_uris setting to the user
// for mobile apps this is likely to be something like protocol://logout
if (!postLogoutRedirectUris.isEmpty()) {
oauthClient.setPostLogoutRedirectUris(postLogoutRedirectUris);
}

app = client.createApplication(app);
assignAppToEveryoneGroup(client, app);
Expand All @@ -115,7 +107,7 @@ public ExtensibleResource createOidcNativeApp(Client client, String oidcAppName,
}

@Override
public ExtensibleResource createOidcSpaApp(Client client, String oidcAppName, List<String> redirectUris) {
public ExtensibleResource createOidcSpaApp(Client client, String oidcAppName, List<String> redirectUris, List<String> postLogoutRedirectUris) {

Optional<Application> existingApp = getApplication(client, oidcAppName);

Expand All @@ -128,16 +120,9 @@ public ExtensibleResource createOidcSpaApp(Client client, String oidcAppName, Li
.setGrantTypes(Collections.singletonList(OAuthGrantType.AUTHORIZATION_CODE))
.setApplicationType(OpenIdConnectApplicationType.BROWSER);

// TODO expose this setting to the user
// TODO the post redirect URI should be exposed in v2 of the SDK
Set<String> postLogoutRedirect = redirectUris.stream()
.map(redirectUri -> {
URI uri = URI.create(redirectUri).resolve("/");
return uri.toString();
})
.collect(Collectors.toSet());
if (!postLogoutRedirect.isEmpty()) {
oauthClient.put("post_logout_redirect_uris", new ArrayList<>(postLogoutRedirect));
List<String> logoutRedirectUris = getOrDefaultWebBasedPostLogoutRedirectUris(postLogoutRedirectUris, redirectUris);
if (!logoutRedirectUris.isEmpty()) {
oauthClient.setPostLogoutRedirectUris(logoutRedirectUris);
}

Application app = client.instantiate(OpenIdConnectApplication.class)
Expand Down Expand Up @@ -203,4 +188,20 @@ private void assignAppToEveryoneGroup(Client client, Application app) {
ApplicationGroupAssignment aga = client.instantiate(ApplicationGroupAssignment.class).setPriority(2);
app.createApplicationGroupAssignment(everyoneGroupId, aga);
}

private List<String> getOrDefaultWebBasedPostLogoutRedirectUris(List<String> postLogoutRedirectUris, List<String> redirectUris) {

if (com.okta.commons.lang.Collections.isEmpty(postLogoutRedirectUris)) {
// default to using the redirect URIs base URL
return redirectUris.stream()
.map(redirectUri -> {
URI uri = URI.create(redirectUri).resolve("/");
return uri.toString();
})
.distinct()
.collect(Collectors.toList());
} else {
return postLogoutRedirectUris;
}
}
}
Loading

0 comments on commit f3f464d

Please sign in to comment.