diff options
author | Aijana Schumann <aijana.schumann@highstreet-technologies.com> | 2021-02-15 18:22:28 +0100 |
---|---|---|
committer | Aijana Schumann <aijana.schumann@highstreet-technologies.com> | 2021-02-15 18:23:57 +0100 |
commit | 8515052e1a6de2de56effbc61c73d3aa80169a93 (patch) | |
tree | 8707b2b587890522b35cd7dd1b54ce4f006f1c3a /sdnr/wt/odlux/framework/src | |
parent | db20d36689c011333ed7216b64d3e987e473f1ee (diff) |
Add OAuth support to odlux
Extend odlux to support oauth, support external login provider for sign-in
Issue-ID: CCSDK-3167
Signed-off-by: Aijana Schumann <aijana.schumann@highstreet-technologies.com>
Change-Id: Id5772e0026fa7ebda22c41c2620a7868598f41aa
Diffstat (limited to 'sdnr/wt/odlux/framework/src')
19 files changed, 347 insertions, 90 deletions
diff --git a/sdnr/wt/odlux/framework/src/actions/authentication.ts b/sdnr/wt/odlux/framework/src/actions/authentication.ts index b28100035..de8093573 100644 --- a/sdnr/wt/odlux/framework/src/actions/authentication.ts +++ b/sdnr/wt/odlux/framework/src/actions/authentication.ts @@ -16,11 +16,18 @@ * ============LICENSE_END========================================================================== */ import { Action } from '../flux/action'; -import { AuthToken } from '../models/authentication'; +import { AuthPolicy, User } from '../models/authentication'; -export class UpdateAuthentication extends Action { +export class UpdateUser extends Action { - constructor (public bearerToken: AuthToken | null) { + constructor (public user?: User) { + super(); + } +} + +export class UpdatePolicies extends Action { + + constructor (public authPolicies?: AuthPolicy[]) { super(); } }
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/actions/loginProvider.ts b/sdnr/wt/odlux/framework/src/actions/loginProvider.ts new file mode 100644 index 000000000..e6467111e --- /dev/null +++ b/sdnr/wt/odlux/framework/src/actions/loginProvider.ts @@ -0,0 +1,36 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { Action } from '../flux/action'; +import { Dispatch } from '../flux/store'; + +import { IApplicationStoreState } from '../store/applicationStore'; +import { ExternalLoginProvider } from '../models/externalLoginProvider'; + +import authenticationService from '../services/authenticationService'; + +export class SetExternalLoginProviderAction extends Action { + constructor(public externalLoginProvders: ExternalLoginProvider[] | null) { + super(); + } +} + +export const updateExternalLoginProviderAsyncActionCreator = () => async (dispatch: Dispatch, getState: () => IApplicationStoreState ) => { + const providers = await authenticationService.getAvaliableExteralProvider(); + dispatch(new SetExternalLoginProviderAction(providers || null)); +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/app.tsx b/sdnr/wt/odlux/framework/src/app.tsx index 6a24bfb2a..23ae2fbc9 100644 --- a/sdnr/wt/odlux/framework/src/app.tsx +++ b/sdnr/wt/odlux/framework/src/app.tsx @@ -23,7 +23,10 @@ import { MuiThemeProvider } from '@material-ui/core/styles'; import { Frame } from './views/frame';
+import { User } from './models/authentication';
+
import { AddErrorInfoAction } from './actions/errorActions';
+import { UpdateUser } from './actions/authentication';
import { applicationStoreCreator } from './store/applicationStore';
import { ApplicationStoreProvider } from './flux/connect';
@@ -31,12 +34,11 @@ import { ApplicationStoreProvider } from './flux/connect'; import { startHistoryListener } from './middleware/navigation';
import { startRestService } from './services/restService';
+import { startForceLogoutService } from './services/forceLogoutService';
+import { startNotificationService } from './services/notificationService';
import theme from './design/default';
import '!style-loader!css-loader!./app.css';
-import { ReplaceAction } from './actions/navigationActions';
-import { startForceLogoutService } from './services/forceLogoutService';
-import { startNotificationService } from './services/notificationService';
declare module '@material-ui/core/styles/createMuiTheme' {
@@ -57,9 +59,13 @@ declare module '@material-ui/core/styles/createMuiTheme' { }
}
+export { configureApplication } from "./handlers/applicationStateHandler";
+
export const transportPCEUrl = "transportPCEUrl";
export const runApplication = () => {
+
+ const initialToken = localStorage.getItem("userToken");
const applicationStore = applicationStoreCreator();
window.onerror = function (msg: string, url: string, line: number, col: number, error: Error) {
@@ -81,7 +87,6 @@ export const runApplication = () => { startHistoryListener(applicationStore);
startForceLogoutService(applicationStore);
startNotificationService(applicationStore);
- addTransportPCEUrl();
const App = (): JSX.Element => (
<ApplicationStoreProvider applicationStore={applicationStore} >
@@ -93,12 +98,19 @@ export const runApplication = () => { ReactDOM.render(<App />, document.getElementById('app'));
+ if (initialToken) {
+ applicationStore.dispatch(new UpdateUser(User.fromString(initialToken) || undefined));
+ }
+
};
-const addTransportPCEUrl = () =>{
- const url = window.localStorage.getItem(transportPCEUrl);
- if(url === null){
- window.localStorage.setItem(transportPCEUrl, "http://10.20.6.32:18082/");
- console.log("set transport url :D")
+if (process.env.NODE_ENV === "development") {
+ const addTransportPCEUrl = () =>{
+ const url = window.localStorage.getItem(transportPCEUrl);
+ if(url === null){
+ window.localStorage.setItem(transportPCEUrl, "http://10.20.6.32:18082/");
+ console.log("set transport url :D")
+ }
}
+ addTransportPCEUrl();
}
diff --git a/sdnr/wt/odlux/framework/src/assets/version.json b/sdnr/wt/odlux/framework/src/assets/version.json index 260aec4ea..6d4eb0cc6 100644 --- a/sdnr/wt/odlux/framework/src/assets/version.json +++ b/sdnr/wt/odlux/framework/src/assets/version.json @@ -1,4 +1,4 @@ { - "version":"88.1c38886(20/12/04)", - "build":"2020-12-04T06:06:24Z" + "version":"56.139cd6d(20/07/08)", + "build":"2020-07-16T06:06:24Z" }
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/components/titleBar.tsx b/sdnr/wt/odlux/framework/src/components/titleBar.tsx index 49c096691..62db1de40 100644 --- a/sdnr/wt/odlux/framework/src/components/titleBar.tsx +++ b/sdnr/wt/odlux/framework/src/components/titleBar.tsx @@ -35,7 +35,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBan } from '@fortawesome/free-solid-svg-icons';
import { faDotCircle } from '@fortawesome/free-solid-svg-icons';
-import { UpdateAuthentication } from '../actions/authentication';
+import { UpdateUser } from '../actions/authentication';
import { ReplaceAction } from '../actions/navigationActions';
import connect, { Connect, IDispatcher } from '../flux/connect';
@@ -71,7 +71,7 @@ const styles = (theme: Theme) => createStyles({ const mapDispatch = (dispatcher: IDispatcher) => {
return {
logout: () => {
- dispatcher.dispatch(new UpdateAuthentication(null));
+ dispatcher.dispatch(new UpdateUser(undefined));
dispatcher.dispatch(new ReplaceAction("/login"));
},
toggleMainMenu: (value: boolean, value2: boolean) => {
diff --git a/sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts b/sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts index a93f96a82..b5c1ee7b1 100644 --- a/sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts +++ b/sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts @@ -16,16 +16,19 @@ * ============LICENSE_END========================================================================== */ import { IActionHandler } from '../flux/action'; -import { SetTitleAction } from '../actions/titleActions'; +import { SetTitleAction } from '../actions/titleActions'; +import { SetExternalLoginProviderAction } from '../actions/loginProvider'; import { AddSnackbarNotification, RemoveSnackbarNotification } from '../actions/snackbarActions'; import { AddErrorInfoAction, RemoveErrorInfoAction, ClearErrorInfoAction } from '../actions/errorActions'; import { MenuAction, MenuClosedByUser } from '../actions/menuAction' -import { IconType } from '../models/iconDefinition'; +import { SetWebsocketAction } from '../actions/websocketAction'; +import { IconType } from '../models/iconDefinition'; import { ErrorInfo } from '../models/errorInfo'; import { SnackbarItem } from '../models/snackbarItem'; -import { SetWebsocketAction } from '../actions/websocketAction'; +import { ExternalLoginProvider } from '../models/externalLoginProvider'; +import { ApplicationConfig } from '../models/applicationConfig'; export interface IApplicationState { title: string; @@ -36,9 +39,27 @@ export interface IApplicationState { errors: ErrorInfo[]; snackBars: SnackbarItem[]; isWebsocketAvailable: boolean | undefined; + externalLoginProviders: ExternalLoginProvider[] | null; + authentication: "basic"|"oauth", // basic + enablePolicy: boolean // false } -const applicationStateInit: IApplicationState = { title: "Loading ...", errors: [], snackBars: [], isMenuOpen: true, isMenuClosedByUser: false, isWebsocketAvailable: undefined }; +const applicationStateInit: IApplicationState = { + title: "Loading ...", + errors: [], + snackBars: [], + isMenuOpen: true, + isMenuClosedByUser: false, + isWebsocketAvailable: undefined, + externalLoginProviders: null, + authentication: "basic", + enablePolicy: false, +}; + +export const configureApplication = (config: ApplicationConfig) => { + applicationStateInit.authentication = config.authentication === "oauth" ? "oauth" : "basic"; + applicationStateInit.enablePolicy = config.authentication ? true : false; +} export const applicationStateHandler: IActionHandler<IApplicationState> = (state = applicationStateInit, action) => { if (action instanceof SetTitleAction) { @@ -46,14 +67,14 @@ export const applicationStateHandler: IActionHandler<IApplicationState> = (state ...state, title: action.title, icon: action.icon, - appId: action.appId + appId: action.appId, }; } else if (action instanceof AddErrorInfoAction) { state = { ...state, errors: [ ...state.errors, - action.errorInfo + action.errorInfo, ] }; } else if (action instanceof RemoveErrorInfoAction) { @@ -63,7 +84,7 @@ export const applicationStateHandler: IActionHandler<IApplicationState> = (state ...state, errors: [ ...state.errors.slice(0, index), - ...state.errors.slice(index + 1) + ...state.errors.slice(index + 1), ] }; } @@ -71,7 +92,7 @@ export const applicationStateHandler: IActionHandler<IApplicationState> = (state if (state.errors && state.errors.length) { state = { ...state, - errors: [] + errors: [], }; } } else if (action instanceof AddSnackbarNotification) { @@ -79,29 +100,34 @@ export const applicationStateHandler: IActionHandler<IApplicationState> = (state ...state, snackBars: [ ...state.snackBars, - action.notification + action.notification, ] }; } else if (action instanceof RemoveSnackbarNotification) { state = { ...state, - snackBars: state.snackBars.filter(s => s.key !== action.key) + snackBars: state.snackBars.filter(s => s.key !== action.key), }; } else if (action instanceof MenuAction) { state = { ...state, - isMenuOpen: action.isOpen + isMenuOpen: action.isOpen, } } else if (action instanceof MenuClosedByUser) { state = { ...state, - isMenuClosedByUser: action.isClosed + isMenuClosedByUser: action.isClosed, } } else if (action instanceof SetWebsocketAction) { state = { ...state, - isWebsocketAvailable: action.isConnected + isWebsocketAvailable: action.isConnected, + } + } else if (action instanceof SetExternalLoginProviderAction){ + state = { + ...state, + externalLoginProviders: action.externalLoginProvders, } } return state; diff --git a/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts b/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts index 82b228dc0..5217bd414 100644 --- a/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts +++ b/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts @@ -16,31 +16,26 @@ * ============LICENSE_END========================================================================== */ import { IActionHandler } from '../flux/action'; -import { UpdateAuthentication } from '../actions/authentication'; +import { UpdatePolicies, UpdateUser } from '../actions/authentication'; -import { User } from '../models/authentication'; +import { AuthPolicy, User } from '../models/authentication'; import { onLogin, onLogout } from '../services/applicationApi'; import { startWebsocketSession, endWebsocketSession } from '../services/notificationService'; export interface IAuthenticationState { user?: User; -} - -const initialToken = localStorage.getItem("userToken"); - -if (initialToken !== null) { - startWebsocketSession(); + policies?: AuthPolicy[]; } const authenticationStateInit: IAuthenticationState = { - user: initialToken && User.fromString(initialToken) || undefined + user: undefined }; export const authenticationStateHandler: IActionHandler<IAuthenticationState> = (state = authenticationStateInit, action) => { - if (action instanceof UpdateAuthentication) { - - const user = action.bearerToken && new User(action.bearerToken) || undefined; + if (action instanceof UpdateUser) { + const {user} = action; + if (user) { localStorage.setItem("userToken", user.toString()); startWebsocketSession(); @@ -53,9 +48,14 @@ export const authenticationStateHandler: IActionHandler<IAuthenticationState> = state = { ...state, - user + user, }; + } else if (action instanceof UpdatePolicies) { + state = { + ...state, + policies: action.authPolicies, + }; } - return state; }; + diff --git a/sdnr/wt/odlux/framework/src/index.html b/sdnr/wt/odlux/framework/src/index.html index d51c448a9..5cd2805c1 100644 --- a/sdnr/wt/odlux/framework/src/index.html +++ b/sdnr/wt/odlux/framework/src/index.html @@ -16,6 +16,7 @@ <script> // run the application require(["run"], function (run) { + run.configureApplication({ authentication:"oauth", enablePolicy: true,}); run.runApplication(); }); diff --git a/sdnr/wt/odlux/framework/src/middleware/navigation.ts b/sdnr/wt/odlux/framework/src/middleware/navigation.ts index d5cdcd44b..c5ab788f3 100644 --- a/sdnr/wt/odlux/framework/src/middleware/navigation.ts +++ b/sdnr/wt/odlux/framework/src/middleware/navigation.ts @@ -15,15 +15,19 @@ * the License. * ============LICENSE_END========================================================================== */ +import * as jwt from 'jsonwebtoken'; import { Location, History, createHashHistory } from "history"; -import { ApplicationStore } from "../store/applicationStore"; -import { Dispatch } from '../flux/store'; +import { User } from "../models/authentication"; import { LocationChanged, NavigateToApplication } from "../actions/navigationActions"; import { PushAction, ReplaceAction, GoAction, GoBackAction, GoForwardeAction } from '../actions/navigationActions'; import { applicationManager } from "../services/applicationManager"; +import { UpdateUser } from "../actions/authentication"; + +import { ApplicationStore } from "../store/applicationStore"; +import { Dispatch } from '../flux/store'; const routerMiddlewareCreator = (history: History) => () => (next: Dispatch): Dispatch => (action) => { @@ -49,7 +53,16 @@ const routerMiddlewareCreator = (history: History) => () => (next: Dispatch): Di history.goForward(); } else if (action instanceof LocationChanged) { // ensure user is logged in and token is valid - if (!action.pathname.startsWith("/login") && applicationStore && (!applicationStore.state.framework.authenticationState.user || !applicationStore.state.framework.authenticationState.user.isValid)) { + if (action.pathname.startsWith("/oauth") && (action.search.startsWith("?token="))){ + const ind = action.search.lastIndexOf("token="); + const tokenStr = ind > -1 ? action.search.substr(ind+6) : null; + const token = tokenStr && jwt.decode(tokenStr); + if (tokenStr && token) { + // @ts-ignore + const user = new User({ username: token["name"], access_token: tokenStr, token_type: "Bearer", expires: (new Date().valueOf()) + ( (+token['exp']) * 1000) }) || undefined; + return next(new UpdateUser(user)) as any; + } + } if (!action.pathname.startsWith("/login") && applicationStore && (!applicationStore.state.framework.authenticationState.user || !applicationStore.state.framework.authenticationState.user.isValid)) { history.replace(`/login?returnTo=${action.pathname}`); } else { return next(action); diff --git a/sdnr/wt/odlux/framework/src/middleware/policies.ts b/sdnr/wt/odlux/framework/src/middleware/policies.ts new file mode 100644 index 000000000..662ecddb3 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/middleware/policies.ts @@ -0,0 +1,41 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import authenticationService from '../services/authenticationService'; + +import { UpdateUser, UpdatePolicies } from '../actions/authentication'; +import { Dispatch } from '../flux/store'; +import { MiddlewareApi } from '../store/applicationStore'; + +function updatePoliciesMiddleware() { + return ({ dispatch, getState }: MiddlewareApi) => + (next : Dispatch) : Dispatch => + action => { + const { framework: { applicationState: { enablePolicy } } } = getState() || { framework: { applicationState: { } } }; + if (enablePolicy && action instanceof UpdateUser) { + next(action); + authenticationService.getAccessPolicies().then((policies) => dispatch(new UpdatePolicies(policies||undefined))); + return action; + } + if (enablePolicy === false) next(new UpdatePolicies()); + return next(action); + }; +} + +export const updatePolicies = updatePoliciesMiddleware(); +export default updatePolicies;
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/models/applicationConfig.ts b/sdnr/wt/odlux/framework/src/models/applicationConfig.ts new file mode 100644 index 000000000..5224840eb --- /dev/null +++ b/sdnr/wt/odlux/framework/src/models/applicationConfig.ts @@ -0,0 +1,4 @@ +export type ApplicationConfig = { + authentication: "basic"|"oauth", // basic + enablePolicy: false // false +};
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/models/authentication.ts b/sdnr/wt/odlux/framework/src/models/authentication.ts index a50c5ded4..b6840a0ce 100644 --- a/sdnr/wt/odlux/framework/src/models/authentication.ts +++ b/sdnr/wt/odlux/framework/src/models/authentication.ts @@ -23,6 +23,16 @@ export type AuthToken = { expires: number; } +export type AuthPolicy = { + path: string; + methods: { + get?: boolean; + post?: boolean; + put?: boolean; + patch?: boolean; + delete?: boolean; + } +} export class User { diff --git a/sdnr/wt/odlux/framework/src/models/externalLoginProvider.ts b/sdnr/wt/odlux/framework/src/models/externalLoginProvider.ts new file mode 100644 index 000000000..357feed06 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/models/externalLoginProvider.ts @@ -0,0 +1,23 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +export type ExternalLoginProvider = { + id: string; + title: string; + loginUrl: string; + }
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/run.ts b/sdnr/wt/odlux/framework/src/run.ts index 10692e8d6..991b7494e 100644 --- a/sdnr/wt/odlux/framework/src/run.ts +++ b/sdnr/wt/odlux/framework/src/run.ts @@ -15,4 +15,5 @@ * the License. * ============LICENSE_END========================================================================== */ +export { configureApplication } from './handlers/applicationStateHandler'; export { runApplication } from './app'; diff --git a/sdnr/wt/odlux/framework/src/services/authenticationService.ts b/sdnr/wt/odlux/framework/src/services/authenticationService.ts index 9b006fc16..4e7d109d9 100644 --- a/sdnr/wt/odlux/framework/src/services/authenticationService.ts +++ b/sdnr/wt/odlux/framework/src/services/authenticationService.ts @@ -15,19 +15,30 @@ * the License. * ============LICENSE_END========================================================================== */ +import { AuthPolicy, AuthToken } from "../models/authentication"; +import { ExternalLoginProvider } from "../models/externalLoginProvider"; + import { requestRest, formEncode } from "./restService"; -import { AuthToken } from "../models/authentication"; type AuthTokenResponse = { access_token: string; token_type: string; - expires_in: number; + expires_at: number; } - class AuthenticationService { + public async getAvaliableExteralProvider() { + const result = await requestRest<ExternalLoginProvider[]>(`oauth/providers`, { + method: "GET", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + }, false); + return result; + } + public async authenticateUserOAuth(email: string, password: string, scope: string): Promise<AuthToken | null> { - const result = await requestRest<string>(`oauth2/token`, { + const result = await requestRest<AuthTokenResponse>(`oauth/login`, { method: "POST", headers: { 'Content-Type': 'application/x-www-form-urlencoded' @@ -39,12 +50,11 @@ class AuthenticationService { scope: scope }) }, false); - const resultObj: AuthTokenResponse| null = result && JSON.parse(result); - return resultObj && { + return result && { username: email, - access_token: resultObj.access_token, - token_type: resultObj.token_type, - expires: (new Date().valueOf()) + (resultObj.expires_in * 1000) + access_token: result.access_token, + token_type: result.token_type, + expires: (result.expires_at * 1000) } || null; } @@ -65,6 +75,10 @@ class AuthenticationService { } return null; } + + public async getAccessPolicies(){ + return await requestRest<AuthPolicy[]>(`oauth/policies`, { method: "GET" }, true); + } } export const authenticationService = new AuthenticationService(); diff --git a/sdnr/wt/odlux/framework/src/services/forceLogoutService.ts b/sdnr/wt/odlux/framework/src/services/forceLogoutService.ts index 0c7c38dff..a57739025 100644 --- a/sdnr/wt/odlux/framework/src/services/forceLogoutService.ts +++ b/sdnr/wt/odlux/framework/src/services/forceLogoutService.ts @@ -17,7 +17,7 @@ */ import { ApplicationStore } from "../store/applicationStore"; -import { UpdateAuthentication } from "../actions/authentication"; +import { UpdateUser } from "../actions/authentication"; import { ReplaceAction } from "../actions/navigationActions"; const maxMinutesTillLogout = 15; @@ -45,7 +45,7 @@ const createForceLogoutInterval = () => { if (tickTimer === 0) { console.log("got logged out by timer") if (applicationStore) { - applicationStore.dispatch(new UpdateAuthentication(null)); + applicationStore.dispatch(new UpdateUser(undefined)); applicationStore.dispatch(new ReplaceAction("/login")); } } diff --git a/sdnr/wt/odlux/framework/src/store/applicationStore.ts b/sdnr/wt/odlux/framework/src/store/applicationStore.ts index 287eba31c..a4545eff9 100644 --- a/sdnr/wt/odlux/framework/src/store/applicationStore.ts +++ b/sdnr/wt/odlux/framework/src/store/applicationStore.ts @@ -32,6 +32,7 @@ import apiMiddleware from '../middleware/api'; import thunkMiddleware from '../middleware/thunk'; import loggerMiddleware from '../middleware/logger'; import routerMiddleware from '../middleware/navigation'; +import { updatePolicies } from '../middleware/policies'; export type MiddlewareApi = MiddlewareArg<IApplicationStoreState>; @@ -65,7 +66,7 @@ export const applicationStoreCreator = (): ApplicationStore => { return acc; }, { framework: frameworkHandlers } as any); - const applicationStore = new ApplicationStore(combineActionHandler(actionHandlers), chainMiddleware(loggerMiddleware, thunkMiddleware, routerMiddleware, apiMiddleware, ...middlewares)); + const applicationStore = new ApplicationStore(combineActionHandler(actionHandlers), chainMiddleware(loggerMiddleware, thunkMiddleware, routerMiddleware, apiMiddleware, updatePolicies, ...middlewares)); setApplicationStore(applicationStore); return applicationStore; } diff --git a/sdnr/wt/odlux/framework/src/views/frame.tsx b/sdnr/wt/odlux/framework/src/views/frame.tsx index c8e24fdb9..b4cc43e0b 100644 --- a/sdnr/wt/odlux/framework/src/views/frame.tsx +++ b/sdnr/wt/odlux/framework/src/views/frame.tsx @@ -80,32 +80,32 @@ class FrameComponent extends React.Component<FrameProps>{ }
<Switch>
<Route exact path="/" component={() => (
- <AppFrame title={"Home"} icon={faHome} >
- <Home />
- </AppFrame>
+ <AppFrame title={"Home"} icon={faHome} >
+ <Home />
+ </AppFrame>
)} />
<Route path="/about" component={() => (
- <AppFrame title={"About"} icon={faAddressBook} >
- <About />
- </AppFrame>
+ <AppFrame title={"About"} icon={faAddressBook} >
+ <About />
+ </AppFrame>
)} />
{process.env.NODE_ENV === "development" ? <Route path="/test" component={() => (
- <AppFrame title={"Test"} icon={faAddressBook} >
- <Test />
- </AppFrame>
+ <AppFrame title={"Test"} icon={faAddressBook} >
+ <Test />
+ </AppFrame>
)} /> : null}
<Route path="/login" component={() => (
- <AppFrame title={"Login"} icon={faSignInAlt} >
- <Login />
- </AppFrame>
- )} />
- {Object.keys(registrations).map(p => {
- const application = registrations[p];
- return (<Route key={application.name} path={application.path || `/${application.name}`} component={() => (
- <AppFrame title={application.title || (typeof application.menuEntry === 'string' && application.menuEntry) || application.name} icon={application.icon} appId={application.name} >
- <application.rootComponent />
- </AppFrame>
- )} />)
+ <AppFrame title={"Login"} icon={faSignInAlt} >
+ <Login />
+ </AppFrame>
+ )} />
+ { Object.keys(registrations).map(p => {
+ const application = registrations[p];
+ return (<Route key={application.name} path={application.path || `/${application.name}`} component={() => (
+ <AppFrame title={application.title || (typeof application.menuEntry === 'string' && application.menuEntry) || application.name} icon={application.icon} appId={application.name} >
+ <application.rootComponent />
+ </AppFrame>
+ )} />)
})}
<Redirect to="/" />
</Switch>
diff --git a/sdnr/wt/odlux/framework/src/views/login.tsx b/sdnr/wt/odlux/framework/src/views/login.tsx index b06cf7631..be1fb801f 100644 --- a/sdnr/wt/odlux/framework/src/views/login.tsx +++ b/sdnr/wt/odlux/framework/src/views/login.tsx @@ -32,10 +32,16 @@ import Paper from '@material-ui/core/Paper'; import Typography from '@material-ui/core/Typography'; import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles'; -import connect, { Connect } from '../flux/connect'; +import connect, { Connect, IDispatcher } from '../flux/connect'; import authenticationService from '../services/authenticationService'; -import { UpdateAuthentication } from '../actions/authentication'; +import { updateExternalLoginProviderAsyncActionCreator } from '../actions/loginProvider'; +import { UpdatePolicies, UpdateUser } from '../actions/authentication'; + +import { IApplicationStoreState } from '../store/applicationStore'; +import { AuthPolicy, AuthToken, User } from '../models/authentication'; +import Menu from '@material-ui/core/Menu'; +import { MenuItem } from '@material-ui/core'; const styles = (theme: Theme) => createStyles({ layout: { @@ -69,14 +75,37 @@ const styles = (theme: Theme) => createStyles({ }, }); -type LoginProps = RouteComponentProps<{}> & WithStyles<typeof styles> & Connect; +const mapProps = (state: IApplicationStoreState) => ({ + search: state.framework.navigationState.search, + authentication: state.framework.applicationState.authentication, + externalLoginProviders: state.framework.applicationState.externalLoginProviders , +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + updateExternalProviders: () => dispatcher.dispatch(updateExternalLoginProviderAsyncActionCreator()), + updateAuthentication: (token: AuthToken | null) => { + const user = token && new User(token) || undefined; + dispatcher.dispatch(new UpdateUser(user)); + }, + updatePolicies: (policies?: AuthPolicy[]) => { + return dispatcher.dispatch(new UpdatePolicies(policies)); + }, +}); + +type LoginProps = RouteComponentProps<{}> & WithStyles<typeof styles> & Connect<typeof mapProps, typeof mapDispatch>; interface ILoginState { + externalProviderAnchor: HTMLElement | null; busy: boolean; username: string; password: string; scope: string; message: string; + providers: { + id: string; + title: string; + loginUrl: string; + }[] | null; } @@ -87,14 +116,26 @@ class LoginComponent extends React.Component<LoginProps, ILoginState> { super(props); this.state = { + externalProviderAnchor: null, busy: false, username: '', password: '', scope: 'sdn', - message: '' + message: '', + providers: null, }; } + async componentDidMount(){ + if (this.props.authentication === "oauth" && (this.props.externalLoginProviders == null || this.props.externalLoginProviders.length === 0)){ + this.props.updateExternalProviders(); + } + } + + private setExternalProviderAnchor = (el: HTMLElement | null) => { + this.setState({externalProviderAnchor: el }) + } + render(): JSX.Element { const { classes } = this.props; return ( @@ -153,6 +194,33 @@ class LoginComponent extends React.Component<LoginProps, ILoginState> { > Sign in </Button> + { this.props.externalLoginProviders && this.props.externalLoginProviders.length > 0 + ? + [ + <Button + aria-controls="externalLogin" + aria-haspopup="true" + fullWidth + variant="contained" + color="primary" + className={classes.submit} onClick={(ev) => { this.setExternalProviderAnchor(ev.currentTarget); }}> + Use external Login + </Button>, + <Menu + anchorEl={this.state.externalProviderAnchor} + keepMounted + open={Boolean(this.state.externalProviderAnchor)} + onClose={() => { this.setExternalProviderAnchor(null); }} + > + { + this.props.externalLoginProviders.map((provider) => ( + <MenuItem key={provider.id} onClick={() => { window.location = provider.loginUrl as any; } }>{ provider.title} </MenuItem> + )) + } + </Menu> + ] + : null + } </form> {this.state.message && <Alert severity="error">{this.state.message}</Alert>} </Paper> @@ -165,16 +233,16 @@ class LoginComponent extends React.Component<LoginProps, ILoginState> { event.preventDefault(); this.setState({ busy: true }); - // const token = await authenticationService.authenticateUserOAuth(this.state.username, this.state.password, this.state.scope); - const token = await authenticationService.authenticateUserBasicAuth(this.state.username, this.state.password, this.state.scope); - this.props.dispatch(new UpdateAuthentication(token)); + + const token = this.props.authentication === "oauth" + ? await authenticationService.authenticateUserOAuth(this.state.username, this.state.password, this.state.scope) + : await authenticationService.authenticateUserBasicAuth(this.state.username, this.state.password, this.state.scope); + + this.props.updateAuthentication(token); this.setState({ busy: false }); if (token) { - const query = - this.props.state.framework.navigationState.search && - this.props.state.framework.navigationState.search.replace(/^\?/, "") - .split('&').map(e => e.split("=")); + const query = this.props.search && this.props.search.replace(/^\?/, "").split('&').map(e => e.split("=")); const returnTo = query && query.find(e => e[0] === "returnTo"); this.props.history.replace(returnTo && returnTo[1] || "/"); } @@ -187,5 +255,5 @@ class LoginComponent extends React.Component<LoginProps, ILoginState> { } } -export const Login = withStyles(styles)(withRouter(connect()(LoginComponent))); +export const Login = withStyles(styles)(withRouter(connect(mapProps, mapDispatch)(LoginComponent))); export default Login;
\ No newline at end of file |