Skip to content
This repository has been archived by the owner on Jan 24, 2023. It is now read-only.

Commit

Permalink
Add support for no authentication (#4386)
Browse files Browse the repository at this point in the history
* Add supprot for no authentication

* Fix backend unit tests

* Fix backend test failure

* Address PR feedback

* Address PR feedback

* Fix backend unit test mock

* Turn off auto-logout if the user can not log off

* Ensure local auth user can edit profile

* Fix unit tests

Co-authored-by: Richard Cox <richard.cox@suse.com>
  • Loading branch information
nwmac and richard-cox committed Nov 13, 2020
1 parent c4a10ce commit f41cf5f
Show file tree
Hide file tree
Showing 23 changed files with 378 additions and 53 deletions.
4 changes: 3 additions & 1 deletion src/frontend/packages/core/src/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createBasicStoreModule } from '@stratosui/store/testing';

import { CoreTestingModule } from '../test-framework/core-test.modules';
import { AppComponent } from './app.component';
import { CurrentUserPermissionsService } from './core/permissions/current-user-permissions.service';
import { LoggedInService } from './logged-in.service';
import { SharedModule } from './shared/shared.module';

Expand All @@ -15,7 +16,8 @@ describe('AppComponent', () => {
AppComponent
],
providers: [
LoggedInService
LoggedInService,
CurrentUserPermissionsService,
],
imports: [
SharedModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ import {
export enum StratosCurrentUserPermissions {
ENDPOINT_REGISTER = 'register.endpoint',
PASSWORD_CHANGE = 'change-password',
EDIT_PROFILE = 'edit-profile',
/**
* Does the user have permission to view/create/delete their own API Keys?
*/
API_KEYS = 'api-keys'
API_KEYS = 'api-keys',
CAN_NOT_LOGOUT = 'no-logout'
}

export enum StratosPermissionStrings {
Expand All @@ -36,7 +38,9 @@ export enum StratosPermissionStrings {

export enum StratosScopeStrings {
STRATOS_CHANGE_PASSWORD = 'password.write',
SCIM_READ = 'scim.read'
SCIM_READ = 'scim.read',
SCIM_WRITE = 'scim.write',
STRATOS_NOAUTH = 'stratos.noauth'
}

export enum StratosPermissionTypes {
Expand All @@ -57,7 +61,15 @@ export const stratosPermissionConfigs: IPermissionConfigs = {
StratosPermissionTypes.STRATOS_SCOPE,
StratosScopeStrings.STRATOS_CHANGE_PASSWORD
),
[StratosCurrentUserPermissions.API_KEYS]: new PermissionConfig(StratosPermissionTypes.API_KEY, '')
[StratosCurrentUserPermissions.EDIT_PROFILE]: new PermissionConfig(
StratosPermissionTypes.STRATOS_SCOPE,
StratosScopeStrings.SCIM_WRITE
),
[StratosCurrentUserPermissions.API_KEYS]: new PermissionConfig(StratosPermissionTypes.API_KEY, ''),
[StratosCurrentUserPermissions.CAN_NOT_LOGOUT]: new PermissionConfig(
StratosPermissionTypes.STRATOS_SCOPE,
StratosScopeStrings.STRATOS_NOAUTH
),
};

export class StratosUserPermissionsChecker extends BaseCurrentUserPermissionsChecker implements ICurrentUserPermissionsChecker {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ <h1>User Profile</h1>
</mat-card-header>
<div class="user-profile__options">

<div class="user-profile__option" *ngIf="gravatarEnabled$ | async as enableGravatar">
<div class="user-profile__option-inner">
<div class="user-profile__option" *ngIf="canEdit$ | async">
<div class="user-profile__option-inner" *ngIf="gravatarEnabled$ | async as enableGravatar">
<mat-slide-toggle [checked]="enableGravatar === 'true'" (change)="updateGravatarEnabled(enableGravatar)">
<div class="user-profile__option-header">Use Gravatar for user icon</div>
</mat-slide-toggle>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { SetPollingEnabledAction, SetSessionTimeoutAction } from '../../../../../store/src/actions/dashboard-actions';
import { DashboardOnlyAppState } from '../../../../../store/src/app-state';
import { selectDashboardState } from '../../../../../store/src/selectors/dashboard.selectors';
import { ThemeService } from '../../../../../store/src/theme.service';
import { UserProfileInfo } from '../../../../../store/src/types/user-profile.types';
import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service';
import { StratosCurrentUserPermissions } from '../../../core/permissions/stratos-user-permissions.checker';
import { UserProfileService } from '../../../core/user-profile.service';
import { UserService } from '../../../core/user.service';
import { SetGravatarEnabledAction } from './../../../../../store/src/actions/dashboard-actions';
Expand Down Expand Up @@ -72,11 +74,15 @@ export class ProfileInfoComponent {
private userProfileService: UserProfileService,
private store: Store<DashboardOnlyAppState>,
public userService: UserService,
public themeService: ThemeService
public themeService: ThemeService,
private currentUserPermissionsService: CurrentUserPermissionsService,
) {
this.isError$ = userProfileService.isError$;
this.userProfile$ = userProfileService.userProfile$;
this.canEdit$ = this.isError$.pipe(map(e => !e));

const canEdit = this.isError$.pipe(map(e => !e));
const hasEditPermissions = this.currentUserPermissionsService.can(StratosCurrentUserPermissions.EDIT_PROFILE);
this.canEdit$ = combineLatest([canEdit, hasEditPermissions]).pipe(map(([a, b]) => a && b));

this.primaryEmailAddress$ = this.userProfile$.pipe(
map((profile: UserProfileInfo) => userProfileService.getPrimaryEmailAddress(profile))
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/packages/core/src/logged-in.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createBasicStoreModule } from '@stratosui/store/testing';

import { CoreTestingModule } from '../test-framework/core-test.modules';
import { CoreModule } from './core/core.module';
import { CurrentUserPermissionsService } from './core/permissions/current-user-permissions.service';
import { LoggedInService } from './logged-in.service';

describe('LoggedInService', () => {
Expand All @@ -12,6 +13,7 @@ describe('LoggedInService', () => {
TestBed.configureTestingModule({
providers: [
LoggedInService,
CurrentUserPermissionsService,
],
imports: [
CoreModule,
Expand Down
45 changes: 24 additions & 21 deletions src/frontend/packages/core/src/logged-in.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DOCUMENT } from '@angular/common';
import { Inject, Injectable, NgZone } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { fromEvent, interval, merge, Subscription } from 'rxjs';
import { combineLatest, fromEvent, interval, merge, Subscription } from 'rxjs';
import { tap, withLatestFrom } from 'rxjs/operators';

import { VerifySession } from '../../store/src/actions/auth.actions';
Expand All @@ -12,16 +12,18 @@ import { selectDashboardState } from '../../store/src/selectors/dashboard.select
import { DashboardState } from './../../store/src/reducers/dashboard-reducer';
import { LogOutDialogComponent } from './core/log-out-dialog/log-out-dialog.component';
import { PageVisible } from './core/page-visible';
import { CurrentUserPermissionsService } from './core/permissions/current-user-permissions.service';
import { StratosCurrentUserPermissions } from './core/permissions/stratos-user-permissions.checker';

@Injectable()
export class LoggedInService {
constructor(
@Inject(DOCUMENT) private document: Document,
private store: Store<AppState>,
private dialog: MatDialog,
private ngZone: NgZone
) {
}
private ngZone: NgZone,
private currentUserPermissionsService: CurrentUserPermissionsService,
) { }

private userInteractionChecker: Subscription;

Expand Down Expand Up @@ -53,24 +55,25 @@ export class LoggedInService {
return fromEvent(document, eventName);
});

this.sub = this.store.select(s => s.auth)
.subscribe((auth: AuthState) => {
if (auth.loggedIn && auth.sessionData && auth.sessionData.valid && !auth.error) {
if (!this.sessionChecker || this.sessionChecker.closed) {
this.openSessionCheckerPoll();
}
if (!this.userInteractionChecker) {
this.userInteractionChecker = merge(...eventStreams).subscribe(() => {
this.lastUserInteraction = Date.now();
});
}
} else {
this.closeSessionCheckerPoll();
if (this.userInteractionChecker) {
this.userInteractionChecker.unsubscribe();
}
const auth$ = this.store.select(s => s.auth);
const canNotLogout$ = this.currentUserPermissionsService.can(StratosCurrentUserPermissions.CAN_NOT_LOGOUT);
this.sub = combineLatest([auth$, canNotLogout$]).subscribe(([auth, canNotLogout]) => {
if (!canNotLogout && auth.loggedIn && auth.sessionData && auth.sessionData.valid && !auth.error) {
if (!this.sessionChecker || this.sessionChecker.closed) {
this.openSessionCheckerPoll();
}
if (!this.userInteractionChecker) {
this.userInteractionChecker = merge(...eventStreams).subscribe(() => {
this.lastUserInteraction = Date.now();
});
}
} else {
this.closeSessionCheckerPoll();
if (this.userInteractionChecker) {
this.userInteractionChecker.unsubscribe();
}
});
}
});
}

destroy() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ <h3 class="page-header__history-title">Recent Activity</h3>
<button mat-menu-item routerLink="/api-keys" *ngIf="canAPIKeys$ | async">
<span>API Keys</span>
</button>
<div class="page-header__menu-separator"></div>
<div class="page-header__menu-separator" *ngIf="canLogout$ | async"></div>
</div>
<button mat-menu-item (click)="logout()">
<button mat-menu-item (click)="logout()" *ngIf="canLogout$ | async">
<span>Logout</span>
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export class PageHeaderComponent implements OnDestroy, AfterViewInit {
public username$: Observable<string>;
public user$: Observable<UserProfileInfo>;
public allowGravatar$: Observable<boolean>;
public canLogout$: Observable<boolean>;

public actionsKey: string;

Expand Down Expand Up @@ -162,6 +163,7 @@ export class PageHeaderComponent implements OnDestroy, AfterViewInit {
private userProfileService: UserProfileService,
private cups: CurrentUserPermissionsService,
private endpointsService: EndpointsService,
private currentUserPermissionsService: CurrentUserPermissionsService,
) {
this.events$ = eventService.events$.pipe(
startWith([])
Expand Down Expand Up @@ -199,6 +201,11 @@ export class PageHeaderComponent implements OnDestroy, AfterViewInit {
]).pipe(
map(([disabled, permission]) => !disabled && permission)
);

this.canLogout$ = this.currentUserPermissionsService.can(StratosCurrentUserPermissions.CAN_NOT_LOGOUT).pipe(
map(noLogout => !noLogout)
);

}

ngOnDestroy() {
Expand Down
5 changes: 5 additions & 0 deletions src/jetstream/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ func (p *portalProxy) InitStratosAuthService(t interfaces.AuthEndpointType) erro
databaseConnectionPool: p.DatabaseConnectionPool,
p: p,
}
case interfaces.AuthNone:
auth = &noAuth{
databaseConnectionPool: p.DatabaseConnectionPool,
p: p,
}
default:
err := fmt.Errorf("Invalid auth endpoint type: %v", t)
return err
Expand Down
8 changes: 7 additions & 1 deletion src/jetstream/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,12 @@ func TestVerifySessionNoDate(t *testing.T) {
res, _, ctx, pp, db, _ := setupHTTPTest(req)
defer db.Close()

//Init the auth service
err := pp.InitStratosAuthService(interfaces.Local)
if err != nil {
log.Fatalf("Could not initialise auth service: %v", err)
}

// Set a dummy userid in session - normally the login to UAA would do this.
sessionValues := make(map[string]interface{})
sessionValues["user_id"] = mockUserGUID
Expand All @@ -822,7 +828,7 @@ func TestVerifySessionNoDate(t *testing.T) {
So(errSession, ShouldBeNil)
})

err := pp.verifySession(ctx)
err = pp.verifySession(ctx)
Convey("Should not fail to verify session.", func() {
So(err, ShouldBeNil)
})
Expand Down
10 changes: 9 additions & 1 deletion src/jetstream/authlocal.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ type localAuth struct {
p *portalProxy
}

func (a *localAuth) ShowConfig(config *interfaces.ConsoleConfig) {
log.Infof("... Local User : %s", config.LocalUser)
log.Infof("... Local User Scope : %s", config.LocalUserScope)
}

//Login provides Local-auth specific Stratos login
func (a *localAuth) Login(c echo.Context) error {

Expand Down Expand Up @@ -99,9 +104,10 @@ func (a *localAuth) GetUser(userGUID string) (*interfaces.ConnectedUser, error)
uaaAdmin := (user.Scope == a.p.Config.ConsoleConfig.ConsoleAdminScope)

var scopes []string
scopes = make([]string, 2)
scopes = make([]string, 3)
scopes[0] = user.Scope
scopes[1] = "password.write"
scopes[2] = "scim.write"

connectdUser := &interfaces.ConnectedUser{
GUID: userGUID,
Expand All @@ -113,6 +119,8 @@ func (a *localAuth) GetUser(userGUID string) (*interfaces.ConnectedUser, error)
return connectdUser, nil
}

func (a *localAuth) BeforeVerifySession(c echo.Context) {}

//VerifySession verifies the session the specified local user, currently just verifies user exists
func (a *localAuth) VerifySession(c echo.Context, sessionUser string, sessionExpireTime int64) error {
localUsersRepo, err := localusers.NewPgsqlLocalUsersRepository(a.databaseConnectionPool)
Expand Down
Loading

0 comments on commit f41cf5f

Please sign in to comment.