Skip to content

Commit

Permalink
Code from TL branch
Browse files Browse the repository at this point in the history
  • Loading branch information
davidwatkins73 committed Jan 14, 2024
2 parents e3156f4 + afefa63 commit 24af4f9
Show file tree
Hide file tree
Showing 12 changed files with 360 additions and 7 deletions.
8 changes: 7 additions & 1 deletion docker/waltz.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ jooq.dialect=POSTGRES
database.pool.max=16

waltz.from.email=$WALTZ_FROM_EMAIL
waltz.base.url=$WALTZ_BASE_URL
waltz.base.url=$WALTZ_BASE_URL

oauth.token_url=$OAUTH_TOKEN_URL
oauth.client_secret=$OAUTH_CLIENT_SECRET
oauth.code_verifier=$OAUTH_CODE_VERIFIER
oauth.userinfo_url=$OAUTH_USERINFO_URL
oauth.redirect_uri=$OAUTH_REDIRECT_URI
9 changes: 9 additions & 0 deletions docs/features/configuration/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ Group of settings which control how waltz handles authentication/authorization
* ```web.authentication```
* ```waltz``` - indicates that waltz is handling authentication, the login panel will be displayed
* ```sso``` - authentication is done elsewhere, do not show the login panel
* ```oauth.provider.name``` <-- optional
* this is the name of the oauth2 provider set up in ```oauth.provider.name```
* By default, this is `null`
* ```oauth.provider.details``` <-- optional
* object containing thirdparty OAuth Provider details (see [README](../../../waltz-ng/client/README.md)).
* By default, this is `{name: null}`
* ```oauth.disable.anonymous``` <-- optional
* ```true``` - this will prevent users from viewing Waltz if they are not authenticated via ```sso```
* ```false``` - [default] this allows Anonymous browsing
* ```server.authentication.filter```
* ```<classname>``` - the name of the class which injects the user object into incoming requests.
By default this is ```JWTAuthenticationFilter```, options are:
Expand Down
90 changes: 90 additions & 0 deletions waltz-ng/client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@


# OAuth (Single Sign-On) Implementation

## Overview

The following document outlines *a* solution for integrating OAuth Single Sign-On. It may not be applicable for integrating to all OAuth providers.

All changes are integrated into the Waltz release and are fully-compatible with either ```waltz``` or ```sso``` as the ```web.authentication``` method (see [settings](../../docs/features/configuration/settings.md)).

With this implementation, the users *should* get created automatically. You may want to also assign default user roles via the 'Settings' database table or overrides (see [settings](../../docs/features/configuration/settings.md)).

## Impacted files

* waltz-ng/client/navbar/[svelte-page.html](./navbar/svelte-page.html)
* waltz-ng/client/navbar/[svelte-page.js](./navbar/svelte-page.js)
* waltz-ng/client/[thirdparty-setup.js](./thirdparty-setup.js)
* waltz-web/src/main/java/org/finos/waltz/web/endpoints/auth/[AuthenticationEndpoint.java](../../waltz-web/src/main/java/org/finos/waltz/web/endpoints/auth/AuthenticationEndpoint.java)


## Implementation: svelte-page.html

**Summary:** Add conditional logic to block the page from rendering if the user isn't Authenticated. Some organizations may want to allow for Anonymous browsing for all, but this option would add another layer of protection.

**Setup:** This logic can be enabled via setting ```oauth.disable.anonymous``` to ```true``` (see [settings](../../docs/features/configuration/settings.md)).


## Implementation: svelte-page.js

**Summary:** This section can call the satellizer implementation of the OAuth Provider and attempts to authenticate the user. Conditional logic will set flags to indicate authentication status.

**Setup:** There are the configurations that can be setup (see also [settings](../../docs/features/configuration/settings.md)).

* Waltz Login
* ```web.authentication``` = ```waltz```
* ```oauth.provider.name``` <-- do not add to Settings
* ```oauth.provider.details``` <-- do not add to Settings
* ```oauth.disable.anonymous``` <-- do not add to Settings
* SSO (Externally Managed)
* ```web.authentication``` = ```sso```
* ```oauth.provider.name``` <-- do not add to Settings
* ```oauth.provider.details``` <-- do not add to Settings
* ```oauth.disable.anonymous``` <-- do not add to Settings
* SSO (thirdparty integration with satellizer)
* ```web.authentication``` = ```sso```
* ```oauth.provider.details``` = object containing thirdparty OAuth Provider details
* ```oauth.provider.name``` needs to match the 'name' you provide in `oauth.provider.details`
* ```oauth.disable.anonymous``` can be set to ```true`` (blocks anonymous browsing) or ```false``` (allows anonymous browsing) or left out of Settings (allows anonymous browsing)

Provided below is the framework for an implementation of a thirdparty OAuth Provider. Requirements and therefore, implementations, will vary based on your organizational setup.

Ex. include this in the Settings table for ```oauth.provider.details```

{
name: 'oauthprovider',
clientId: 'Waltz',
url: 'authentication/oauth',
authorizationEndpoint: '',
redirectUri: '',
optionalUrlParams: ['scope', 'save_consent', 'csrf', 'code_challenge', 'code_challenge_method'],
scope: ['userid+email+profile+roles'],
scopeDelimiter: ' ',
saveConsent: '',
csrf: '',
codeChallenge: '',
codeChallengeMethod: 'S256',
oauthType: '2.0',
responseType: 'code',
popupOptions: { width: 600, height: 500 }
}

Notes and Tips:
* A Postman collection could be provided to define and verify all of the header and body requirements for each OAuth request - recreate that format within the requests in Waltz
* The `url` option defines an endpoint we'll be referencing in AuthenticationEndpoint.java - these need to match
* The redirect URI could be generic `window.location.origin`, but this might not work as a 'valid' address for some providers i.e. you must specify the address
* The `optionalUrlParams` are listed on this line, and then assigned a value on the lines below - it's expected that these will vary by organization
* see https://github.com/sahat/satellizer for more details


## Implementation: AuthenticationEndpoint.java

**Summary:** This is the implementation of all of the requests between Waltz and the thirdparty OAuth Provider. This is only a framework for an implementation and will require additional work to integrate for your organization.

**Setup:** Setting up the endpoint and additional functionality for a thirdparty OAuth Provider.

Notes and Tips:
* A Postman collection could be provided to define and verify all of the header and body requirements for each OAuth request - recreate that format within the requests in Waltz
* The `url: 'authentication/oauth'` in thirdparty-setup.js should match the name of the endpoint configured here `Spark.post(WebUtilities.mkPath(BASE_URL, "oauth"), (request, response) -> {`
* Additional user information may be accessible via your external thirdparty OAuth Provider. This implementation can be expanded to request, parse and utilize that info as needed
* paramBuilder and getAccessToken are configured for generic thirdparty OAuth Providers and should work for most imoplementations
12 changes: 12 additions & 0 deletions waltz-ng/client/navbar/svelte-page.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<!-- Header -->
<div ng-if="!$ctrl.oauthDisableAnonymous || (isAuthenticated())">
<div ui-view="header">
</div>

Expand All @@ -23,4 +24,15 @@
<main ui-view="content"
class="main">
</main>
</div>
</div>
<div ng-if="($ctrl.oauthDisableAnonymous === true) && ((!isAuthenticated() && !isAuthFailed))">
<h2>Authenticating user...</h2>
<h3>
<span ng-bind="ctrl.oauthDisableAnonymous"> </span>
</h3>
</div>
<div ng-if="($ctrl.oauthDisableAnonymous === true) && ((!isAuthenticated() && isAuthFailed))">
<h2>Authentication Failed</h2>
<h3>Contact your Waltz System Administrator for more information</h3>
</div>
66 changes: 64 additions & 2 deletions waltz-ng/client/navbar/svelte-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import Toasts from "../notification/components/toaster/Toasts.svelte";
import ToastStore from "../svelte-stores/toast-store"
import Popover from "../common/svelte/popover/Popover.svelte";
import {isIE} from "../common/browser-utils";
import namedSettings from "../system/named-settings";
import {CORE_API} from "../common/services/core-api-utils";

function controller($scope, $timeout, settingsService, $rootScope) {
function controller($q, $scope, $timeout, settingsService, $rootScope, $auth) {
const vm = this;

$scope.isAuthFailed = false;
$scope.oauthProvider = "";

vm.Sidebar = Sidebar;
vm.Toasts = Toasts;
vm.Popover = Popover;
Expand All @@ -27,6 +32,32 @@ function controller($scope, $timeout, settingsService, $rootScope) {
unsubVisible();
};

function getOauthSettings() {

const allowSSOLoginPromise = settingsService
.findOrDefault("web.authentication", "")
.then(r => r);

const oauthProviderNamePromise = settingsService
.findOrDefault(namedSettings.oauthProviderName, null)
.then(r => r);

const disableAnonymousPromise = settingsService
.findOrDefault(namedSettings.oauthDisableAnonymous, false)
.then(r => r);

return $q
.all([allowSSOLoginPromise, oauthProviderNamePromise, disableAnonymousPromise])
.then(([webAuthentication, provider, disableAnonymous]) => {
vm.allowSSOLogin = (webAuthentication === "sso");
vm.oauthProviderName = provider;
vm.oauthDisableAnonymous = (disableAnonymous === "true");
});
}

$scope.isAuthenticated = function() {
return $auth.isAuthenticated();
}

vm.$onInit = () => {
if (isIE()) {
Expand All @@ -36,14 +67,45 @@ function controller($scope, $timeout, settingsService, $rootScope) {
"Waltz is optimised for use in modern browsers. For example Google Chrome, Firefox and Microsoft Edge")
.then(m => ToastStore.info(m, {timeout: 10000}));
}

// Check if sso login is enabled and invoke configured login options
getOauthSettings()
.then(() => {
// sso option is enabled and oauth provider name configured in Settings
if (vm.allowSSOLogin) {
if(vm.oauthProviderName) {
console.log("oauthProviderName is set - Implement thirdparty sso oauth")

// try to authenticate with OAuth Provider
if (!$auth.isAuthenticated()){
$auth.authenticate(vm.oauthProviderName)
.then(function(response){
// console.log("authentication through " + vm.oauthProviderName + " - Successful");
window.location.reload();
})
.catch(function(response){
console.log("authentication through " + vm.oauthProviderName + " - FAILED");
$scope.isAuthFailed = true;
return false;
});
}

} else {
// sso authentication implemented externally
console.log("sso implemented externally")
}
}
});
}
}

controller.$inject = [
"$q",
"$scope",
"$timeout",
"SettingsService",
"$rootScope"
"$rootScope",
"$auth"
];

const component = {
Expand Down
2 changes: 2 additions & 0 deletions waltz-ng/client/system/named-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

export default {
authentication: "web.authentication",
oauthProviderName: "oauth.provider.name",
oauthDisableAnonymous: "oauth.disable.anonymous",
devExtEnabled: "web.devext.enabled",
httpHeaderPrefix: "web.devext.http.header.",
betaEnvironment: "web.beta",
Expand Down
5 changes: 5 additions & 0 deletions waltz-ng/client/thirdparty-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ uiSelectSetup.$inject = ["uiSelectConfig"];


function authProviderSetup($authProvider, BaseUrl) {
// console.log("oauthdetails", {oauthdetails});
$authProvider.baseUrl = BaseUrl;
$authProvider.loginUrl = "/authentication/login";
$authProvider.withCredentials = false;

const oauthProviderDetails = oauthdetails;

$authProvider.google({
clientId: "Google account"
});
Expand All @@ -41,6 +44,8 @@ function authProviderSetup($authProvider, BaseUrl) {
$authProvider.linkedin({
clientId: "LinkedIn Client ID"
});

$authProvider.oauth2(oauthProviderDetails);
}

authProviderSetup.$inject = [
Expand Down
1 change: 1 addition & 0 deletions waltz-ng/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

<!--<% } %>-->

<script src="api/oauthdetails"></script>
<style type="text/css">
@keyframes waltz-splash-scale {
0% { fill: #000 }
Expand Down
11 changes: 11 additions & 0 deletions waltz-web/example.waltz.properties.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
```
# Database Connectivity
database.url=jdbc:mysql://localhost:3306/waltzdb
database.user=waltzadmin
database.password=secret
database.schema=waltztest
database.driver=org.mariadb.jdbc.Driver
jooq.dialect=MARIADB
# Database Performance
database.pool.max=16
# General Waltz Settings
[email protected]
waltz.base.url=http://localhost:8000/
# OAuth Settings
oauth.token_url=https://example.com/access_token
oauth.client_secret=
oauth.code_verifier=
oauth.userinfo_url=https://example.com/userinfo
oauth.redirect_uri=http://localhost:8000/
```
18 changes: 14 additions & 4 deletions waltz-web/src/main/java/org/finos/waltz/web/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,20 @@
import spark.Spark;

import java.util.Map;
import java.util.Optional;
import java.util.TimeZone;

import static java.lang.String.format;
import static org.finos.waltz.common.DateTimeUtilities.UTC;
import static org.finos.waltz.web.WebUtilities.reportException;
import static spark.Spark.after;
import static spark.Spark.before;
import static spark.Spark.options;
import static spark.Spark.port;
import static spark.Spark.*;

public class Main {

private static final Logger LOG = LoggerFactory.getLogger(Main.class);
private final static String GZIP_ENABLED_NAME = "server.gzip.enabled";
private final static String GZIP_MIN_SIZE_NAME = "server.gzip.minimum-size";
private final static String OAUTH_PROVIDER_DETAILS = "oauth.provider.details";

private static AnnotationConfigApplicationContext ctx;

Expand Down Expand Up @@ -113,6 +112,17 @@ void start() {

ctx = new AnnotationConfigApplicationContext(DIConfiguration.class);

get("api/oauthdetails", (req, resp) -> {
resp.header("Content-Type", "application/javascript");
SettingsService settingsService = ctx.getBean(SettingsService.class);

Optional<String> OauthProviderDetails = Optional.of(settingsService
.getValue(OAUTH_PROVIDER_DETAILS)
.orElse("{name : null}"));

return "const oauthdetails = " + OauthProviderDetails.get() + ";";
});

Map<String, Endpoint> endpoints = ctx.getBeansOfType(Endpoint.class);
endpoints.forEach((name, endpoint) -> {
LOG.info("Registering Endpoint: {}", name);
Expand Down
Loading

0 comments on commit 24af4f9

Please sign in to comment.