Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Azure AD support to Explorer #641

Merged
merged 1 commit into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions nflow-explorer/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions nflow-explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"private": true,
"homepage": ".",
"dependencies": {
"@azure/msal-browser": "^3.13.0",
"@azure/msal-react": "^2.0.15",
"@fontsource/roboto": "^4.5.8",
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3",
Expand Down
15 changes: 15 additions & 0 deletions nflow-explorer/public/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,19 @@ var Config = new function() {
* ]
*/
this.searchResultColumns = undefined;

/**
* Microsoft Authentication Library for JavaScript configuration. For more details, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/master/lib/msal-browser/docs/configuration.md
* this.msalConfig = {
* auth: {
* authority: "https://login.microsoftonline.com/ENTER_YOUR_TENANT_ID",
* clientId: "ENTER_YOUR_APPLICATION_ID"
* },
* cache: {
* cacheLocation: "localStorage"
* }
* };
*/
this.msalConfig = undefined;
};
26 changes: 25 additions & 1 deletion nflow-explorer/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
import './index.scss';
import {Config} from "./types";
import {PublicClientApplication, InteractionType} from "@azure/msal-browser";
import {
MsalAuthenticationTemplate,
MsalProvider,
} from "@azure/msal-react";

// see: https://github.com/gregnb/mui-datatables/issues/1893
const oldRender = (TableCell as any).render;
Expand All @@ -37,13 +43,31 @@ const theme = createTheme({
}
});

const wrapMsalIfNeeded = (app: any, config: Config): any => {
if (config.msalConfig) {
console.info('Initializing MSAL')
const authRequest = {
scopes: ["openid"]
};
config.msalClient = new PublicClientApplication(config.msalConfig);
return (
<MsalProvider instance={config.msalClient}>
<MsalAuthenticationTemplate interactionType={InteractionType.Redirect} authenticationRequest={authRequest}>
{app}
</MsalAuthenticationTemplate>
</MsalProvider>
)
}
return app;
}

readConfig().then(config => {
console.info('Config read');
ReactDOM.render(
<React.StrictMode>
<MuiThemeProvider theme={theme}>
<ConfigContext.Provider value={config}>
<App />
{wrapMsalIfNeeded(<App />, config)}
</ConfigContext.Provider>
</MuiThemeProvider>
</React.StrictMode>,
Expand Down
55 changes: 33 additions & 22 deletions nflow-explorer/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,30 @@ const convertWorkflowInstance = (instance: any) => {
);
};

const authenticatedApiCall = (url: string, config: Config, body?: any): Promise<any> => {
const options: RequestInit = {
method: body ? "PUT" : "GET",
body: body
};
if (!config.msalClient) {
return fetch(url, options);
}
const request = {
scopes: ["openid"],
};
config.msalClient.setActiveAccount(config.msalClient.getAllAccounts()[0]) // required by acquireTokenSilent
return config.msalClient.acquireTokenSilent(request)
.then(tokenResponse => {
options["headers"] = new Headers({
"Authorization": "Bearer " + tokenResponse.accessToken,
"content-type": "application/json"
});
return fetch(url, options)
});
}

const listExecutors = (config: Config): Promise<Array<Executor>> => {
return fetch(serviceUrl(config, '/v1/workflow-executor'))
return authenticatedApiCall(serviceUrl(config, '/v1/workflow-executor'), config)
.then(response => response.json())
.then((items: any) => items.map(convertExecutor));
};
Expand All @@ -53,7 +75,7 @@ const listWorkflowDefinitions = (
if (cached) {
return Promise.resolve(cached);
}
return fetch(url)
return authenticatedApiCall(url, config)
.then(response => response.json())
.then((response: Array<WorkflowDefinition>) =>
cacheWD.setAndReturn(url, response)
Expand All @@ -66,7 +88,7 @@ const getWorkflowDefinition = (
): Promise<WorkflowDefinition> => {
const url = serviceUrl(config, '/v1/workflow-definition?type=' + type);
return (
fetch(url)
authenticatedApiCall(url, config)
.then(response => response.json())
// TODO how to handle Not found case?
.then(response => response[0])
Expand All @@ -79,7 +101,7 @@ const getWorkflowStatistics = (
type: string
): Promise<WorkflowStatistics> => {
const url = serviceUrl(config, '/v1/statistics/workflow/' + type);
return fetch(url)
return authenticatedApiCall(url, config)
.then(response => response.json())
.then(response => response.stateStatistics);
};
Expand Down Expand Up @@ -146,7 +168,7 @@ const listWorkflowInstances = (
query?: any
): Promise<WorkflowInstance[]> => {
const params = new URLSearchParams(query).toString();
return fetch(serviceUrl(config, '/v1/workflow-instance?' + params.toString()))
return authenticatedApiCall(serviceUrl(config, '/v1/workflow-instance?' + params.toString()), config)
.then(response => response.json())
.then((items: any) => items.map(convertWorkflowInstance));
};
Expand All @@ -159,7 +181,7 @@ const listChildWorkflowInstances = (
config,
'/v1/workflow-instance?parentWorkflowId=' + id
);
return fetch(url)
return authenticatedApiCall(url, config)
.then(response => response.json())
.then((items: any) => items.map(convertWorkflowInstance));
};
Expand All @@ -174,7 +196,7 @@ const getWorkflowInstance = (
id +
'?include=actions,currentStateVariables,actionStateVariables'
);
return fetch(url)
return authenticatedApiCall(url, config)
.then(response => response.json())
.then(convertWorkflowInstance);
// TODO how to handle Not found case?
Expand All @@ -185,11 +207,8 @@ const createWorkflowInstance = (
data: NewWorkflowInstance
): Promise<NewWorkflowInstanceResponse> => {
const url = serviceUrl(config, '/v1/workflow-instance');
return fetch(url, {
method: 'PUT',
headers: {'content-type': 'application/json'},
body: JSON.stringify(data)
}).then(response => response.json());
return authenticatedApiCall(url, config, JSON.stringify(data))
.then(response => response.json());
};

const updateWorkflowInstance = (
Expand All @@ -198,11 +217,7 @@ const updateWorkflowInstance = (
data: any
): Promise<any> => {
const url = serviceUrl(config, '/v1/workflow-instance/id/' + workflowId);
return fetch(url, {
method: 'PUT',
headers: {'content-type': 'application/json'},
body: JSON.stringify(data)
});
return authenticatedApiCall(url, config, JSON.stringify(data));
};

const sendWorkflowInstanceSignal = (
Expand All @@ -214,11 +229,7 @@ const sendWorkflowInstanceSignal = (
config,
'/v1/workflow-instance/' + workflowId + '/signal'
);
return fetch(url, {
method: 'PUT',
headers: {'content-type': 'application/json'},
body: JSON.stringify(data)
});
return authenticatedApiCall(url, config, JSON.stringify(data));
};

export {
Expand Down
4 changes: 4 additions & 0 deletions nflow-explorer/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {Configuration, PublicClientApplication} from "@azure/msal-browser";

interface Endpoint {
id: string;
title: string;
Expand Down Expand Up @@ -25,6 +27,8 @@ interface Config {
nflowLogoFile?: string;
nflowLogoTitle?: string;
searchResultColumns?: Array<ColumnConfig>;
msalConfig?: Configuration;
msalClient?: PublicClientApplication;
}

interface Executor {
Expand Down