Skip to content

Commit

Permalink
#1591 add SSO via OIDC to Ditto UI
Browse files Browse the repository at this point in the history
* make it configurable which OIDC issuers to use
* re-did a lot of the existing configuration of the Ditto UI
* added typing for Environment
  • Loading branch information
thjaeckle committed Sep 25, 2024
1 parent 0083702 commit a12e220
Show file tree
Hide file tree
Showing 20 changed files with 649 additions and 207 deletions.
22 changes: 11 additions & 11 deletions ui/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ import * as ConnectionsMonitor from './modules/connections/connectionsMonitor.js

import * as Authorization from './modules/environments/authorization.js';
import * as Environments from './modules/environments/environments.js';
import * as Operations from './modules/operations/servicesLogging.js';
import * as Piggyback from './modules/operations/piggyback.js';
import * as Operations from './modules/operations/servicesLogging.js';
import * as Templates from './modules/operations/templates.js';
import * as Policies from './modules/policies/policies.js';
import * as PoliciesJSON from './modules/policies/policiesJSON.js';
import * as PoliciesEntries from './modules/policies/policiesEntries.js';
import * as PoliciesImports from './modules/policies/policiesImports.js';
import * as PoliciesSubjects from './modules/policies/policiesSubjects';
import * as PoliciesJSON from './modules/policies/policiesJSON.js';
import * as PoliciesResources from './modules/policies/policiesResources';
import * as PoliciesSubjects from './modules/policies/policiesSubjects';
import * as Attributes from './modules/things/attributes.js';
import * as FeatureMessages from './modules/things/featureMessages.js';
import * as Features from './modules/things/features.js';
Expand All @@ -51,10 +51,10 @@ let mainNavbar;
document.addEventListener('DOMContentLoaded', async function() {
Utils.ready();
await Things.ready();
ThingsSearch.ready();
ThingsCRUD.ready();
await ThingsSearch.ready();
await ThingsCRUD.ready();
await ThingMessages.ready();
ThingsSSE.ready();
await ThingsSSE.ready();
MessagesIncoming.ready();
Attributes.ready();
await Fields.ready();
Expand All @@ -70,25 +70,25 @@ document.addEventListener('DOMContentLoaded', async function() {
Connections.ready();
ConnectionsCRUD.ready();
await ConnectionsMonitor.ready();
Operations.ready();
await Operations.ready();
Authorization.ready();
await Environments.ready();
Piggyback.ready();
Templates.ready();
await Piggyback.ready();
await Templates.ready();

const thingDescription = WoTDescription({
itemsId: 'tabItemsThing',
contentId: 'tabContentThing',
}, false);
Things.addChangeListener(thingDescription.onReferenceChanged);
thingDescription.ready();
await thingDescription.ready();

const featureDescription = WoTDescription({
itemsId: 'tabItemsFeatures',
contentId: 'tabContentFeatures',
}, true);
Features.addChangeListener(featureDescription.onReferenceChanged);
featureDescription.ready();
await featureDescription.ready();

// make dropdowns not cutting off
new Dropdown(document.querySelector('.dropdown-toggle'), {
Expand Down
49 changes: 28 additions & 21 deletions ui/modules/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import { EventSourcePolyfill } from 'event-source-polyfill';
import * as Environments from './environments/environments.js';
import { AuthMethod } from './environments/environments.js';
import * as Utils from './utils.js';


Expand Down Expand Up @@ -278,31 +279,40 @@ let authHeaderValue;
* Activates authorization header for api calls
* @param {boolean} forDevOps if true, the credentials for the dev ops api will be used.
*/
export function setAuthHeader(forDevOps) {
export function setAuthHeader(forDevOps: boolean) {
let environment = Environments.current();
if (forDevOps) {
if (Environments.current().devopsAuth === 'basic') {
let devopsAuthMethod = environment.authSettings?.devops?.method;
if (devopsAuthMethod === AuthMethod.basic) {
authHeaderKey = 'Authorization';
authHeaderValue = 'Basic ' + window.btoa(Environments.current().usernamePasswordDevOps);
} else if (Environments.current().devopsAuth === 'bearer') {
authHeaderValue = 'Basic ' + window.btoa(environment.authSettings.devops.basic.usernamePassword);
} else if (devopsAuthMethod === AuthMethod.bearer) {
authHeaderKey = 'Authorization';
authHeaderValue ='Bearer ' + Environments.current().bearerDevOps;
authHeaderValue = 'Bearer ' + environment.authSettings.devops.bearer.bearerToken;
} else if (devopsAuthMethod === AuthMethod.oidc) {
authHeaderKey = 'Authorization';
authHeaderValue = 'Bearer ' + environment.authSettings.devops.oidc.bearerToken;
} else {
authHeaderKey = 'Basic';
authHeaderValue = '';
authHeaderKey = 'Authorization';
authHeaderValue = 'Basic';
}
} else {
if (Environments.current().mainAuth === 'basic') {
let mainAuthMethod = environment.authSettings?.main?.method;
if (mainAuthMethod === AuthMethod.basic) {
authHeaderKey = 'Authorization';
authHeaderValue = 'Basic ' + window.btoa(Environments.current().usernamePassword);
} else if (Environments.current().mainAuth === 'pre') {
authHeaderValue = 'Basic ' + window.btoa(environment.authSettings.main.basic.usernamePassword);
} else if (mainAuthMethod === AuthMethod.pre) {
authHeaderKey = 'x-ditto-pre-authenticated';
authHeaderValue = Environments.current().dittoPreAuthenticatedUsername;
} else if (Environments.current().mainAuth === 'bearer') {
authHeaderValue = environment.authSettings.main.pre.dittoPreAuthenticatedUsername;
} else if (mainAuthMethod === AuthMethod.bearer) {
authHeaderKey = 'Authorization';
authHeaderValue ='Bearer ' + Environments.current().bearer;
authHeaderValue = 'Bearer ' + environment.authSettings.main.bearer.bearerToken;
} else if (mainAuthMethod === AuthMethod.oidc) {
authHeaderKey = 'Authorization';
authHeaderValue = 'Bearer ' + environment.authSettings.main.oidc.bearerToken;
} else {
authHeaderKey = 'Basic';
authHeaderValue = '';
authHeaderKey = 'Authorization';
authHeaderValue = 'Basic';
}
}
}
Expand Down Expand Up @@ -409,7 +419,6 @@ export function getEventSource(thingIds, urlParams) {
* @return {*} promise to the result
*/
export async function callConnectionsAPI(operation, successCallback, connectionId = '', connectionJson = null, command = null) {
Utils.assert((env() !== 'things' || Environments.current().solutionId), 'No solutionId configured in environment');
const params = config[env()][operation];
let response;
let body;
Expand All @@ -425,8 +434,8 @@ export async function callConnectionsAPI(operation, successCallback, connectionI
}

try {
response = await fetch(Environments.current().api_uri + params.path.replace('{{solutionId}}',
Environments.current().solutionId).replace('{{connectionId}}', connectionId), {
response = await fetch(Environments.current().api_uri + params.path
.replace('{{connectionId}}', connectionId), {
method: params.method,
headers: {
'Content-Type': operation === 'connectionCommand' ? 'text/plain' : 'application/json',
Expand Down Expand Up @@ -479,9 +488,7 @@ export async function callConnectionsAPI(operation, successCallback, connectionI
}

export function env() {
if (Environments.current().api_uri.startsWith('https://things')) {
return 'things';
} else if (Environments.current().ditto_version === '2') {
if (Environments.current().ditto_version === 2) {
return 'ditto_2';
} else {
return 'ditto_3';
Expand Down
50 changes: 38 additions & 12 deletions ui/modules/environments/authorization.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,23 @@
<div class="row">
<h5>Main authentication</h5>
<hr />
<div class="row">
<div class="row" id="mainOidcSection">
<div class="col-md-5">
<input type="radio" id="main-oidc" name="main-auth-method" value="oidc">
<label for="main-oidc">Single-Sign-On</label>
</div>
<div class="col-md-7">
<div class="input-group">
<label for="oidcProvider" class="input-group-text"><small>SSO provider</small></label>
<select class="form-select form-select-sm me-2" id="oidcProvider"></select>
<button class="btn btn-outline-secondary btn-sm" id="main-oidc-login">Login</button>
<button class="btn btn-outline-secondary btn-sm" id="main-oidc-logout">Logout</button>
</div>
</div>
</div>
<div class="row pt-2" id="mainBasicSection">
<div class="col-md-5">
<input type="radio" id="main-userpass" name="main-auth" value="basic" />
<input type="radio" id="main-userpass" name="main-auth-method" value="basic" />
<label for="main-userpass">Basic</label>
</div>
<div class="col-md-7">
Expand All @@ -37,9 +51,9 @@ <h5>Main authentication</h5>
</div>
</div>
</div>
<div class="row pt-2">
<div class="row pt-2" id="mainBearerSection">
<div class="col-md-5">
<input type="radio" id="main-bearer" name="main-auth" value="bearer"></input>
<input type="radio" id="main-bearer" name="main-auth-method" value="bearer">
<label for="main-bearer">Bearer<br /><small>(JWT OAuth2.0 Bearer token)</small></label>
</div>
<div class="col-md-7">
Expand All @@ -49,9 +63,9 @@ <h5>Main authentication</h5>
</div>
</div>
</div>
<div class="row pt-2">
<div class="row pt-2" id="mainPreAuthenticatedSection">
<div class="col-md-5">
<input type="radio" id="main-pre" name="main-auth" value="pre" />
<input type="radio" id="main-pre" name="main-auth-method" value="pre" />
<label for="main-pre">Pre authenticated<br />
<small>(via HTTP header 'x-ditto-pre-authenticated', must take the form
<code>prefix:suffix</code>)</small>
Expand All @@ -69,9 +83,23 @@ <h5>Main authentication</h5>
<h5>DevOps authentication</h5>
<small>(for Connections and administrative tasks, optional)</small>
<hr />
<div class="row">
<div class="row" id="devOpsOidcSection">
<div class="col-md-5">
<input type="radio" id="devops-oidc" name="devops-auth-method" value="oidc" />
<label for="devops-oidc">Single-Sign-On</label>
</div>
<div class="col-md-7">
<div class="input-group">
<label for="devOpsOidcProvider" class="input-group-text"><small>SSO provider</small></label>
<select class="form-select form-select-sm me-2" id="devOpsOidcProvider"></select>
<button class="btn btn-outline-secondary btn-sm" id="devops-oidc-login">Login</button>
<button class="btn btn-outline-secondary btn-sm" id="devops-oidc-logout">Logout</button>
</div>
</div>
</div>
<div class="row pt-2" id="devOpsBasicSection">
<div class="col-md-5">
<input type="radio" id="devops-userpass" name="devops-auth" value="basic" />
<input type="radio" id="devops-userpass" name="devops-auth-method" value="basic" />
<label for="devops-userpass">Basic<br /></label>
</div>
<div class="col-md-7">
Expand All @@ -85,20 +113,18 @@ <h5>DevOps authentication</h5>
</div>
</div>
</div>
<div class="row pt-2">
<div class="row pt-2" id="devOpsBearerSection">
<div class="col-md-5">
<input type="radio" id="devops-bearer" name="devops-auth" value="bearer" />
<input type="radio" id="devops-bearer" name="devops-auth-method" value="bearer" />
<label for="devops-bearer">Bearer<br /><small>(JWT OAuth2.0 Bearer token)</small></label>
</div>
<div class="col-md-7">
<div class="input-group input-group-sm mb-1">
<label for="bearerDevOps" class="input-group-text">DevOps Bearer</label>
<input type="password" class="form-control form-control-sm" id="bearerDevOps" name="devops-bearer" />
</div>
<div class="input-group input-group-sm mb-1">
</div>
</div>
</div>
</div>
<div class="input-group input-group-sm" style="flex-direction: row-reverse;">
<button class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal"
Expand Down
Loading

0 comments on commit a12e220

Please sign in to comment.