Add the OIDC onboard page

When a user logs in to Harbor for the first time through OIDC, the user will enter an onboard page, prompting the user to add the user name of Harbor. After the user name is entered, click save, and the user successfully logs in to Harbor through OIDC.

Signed-off-by: Yogi_Wang <yawang@vmware.com>
This commit is contained in:
Yogi_Wang 2019-04-03 10:50:29 +08:00
parent e93d931834
commit 3bf644012c
16 changed files with 283 additions and 7 deletions

View File

@ -31,7 +31,7 @@ import { ProjectConfigComponent } from './project/project-config/project-config.
import zh from '@angular/common/locales/zh-Hans';
import es from '@angular/common/locales/es';
import localeFr from '@angular/common/locales/fr';
import { DevCenterComponent } from './dev-center/dev-center.component';
import { OidcOnboardModule } from './oidc-onboard/oidc-onboard.module';
registerLocaleData(zh, 'zh-cn');
registerLocaleData(es, 'es-es');
registerLocaleData(localeFr, 'fr-fr');
@ -60,7 +60,8 @@ export function getCurrentLanguage(translateService: TranslateService) {
AccountModule,
HarborRoutingModule,
ConfigurationModule,
DeveloperCenterModule
DeveloperCenterModule,
OidcOnboardModule
],
exports: [
],

View File

@ -18,6 +18,7 @@ import { SystemAdminGuard } from './shared/route/system-admin-activate.service';
import { AuthCheckGuard } from './shared/route/auth-user-activate.service';
import { SignInGuard } from './shared/route/sign-in-guard-activate.service';
import { MemberGuard } from './shared/route/member-guard-activate.service';
import { OidcGuard } from './shared/route/oidc-guard-active.service';
import { PageNotFoundComponent } from './shared/not-found/not-found.component';
import { HarborShellComponent } from './base/harbor-shell/harbor-shell.component';
@ -51,6 +52,7 @@ import { ProjectRoutingResolver } from './project/project-routing-resolver.servi
import { ListChartsComponent } from './project/helm-chart/list-charts.component';
import { ListChartVersionsComponent } from './project/helm-chart/list-chart-versions/list-chart-versions.component';
import { HelmChartDetailComponent } from './project/helm-chart/helm-chart-detail/chart-detail.component';
import { OidcOnboardComponent } from './oidc-onboard/oidc-onboard.component';
const harborRoutes: Routes = [
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
@ -59,6 +61,11 @@ const harborRoutes: Routes = [
path: 'devcenter',
component: DevCenterComponent
},
{
path: 'oidc-onboard',
component: OidcOnboardComponent,
canActivate: [OidcGuard, SignInGuard]
},
{
path: 'harbor',
component: HarborShellComponent,

View File

@ -0,0 +1,43 @@
<div class="modal">
<div class="modal-dialog" role="dialog">
<div class="modal-content">
<div class="modal-header">
<button (click)="backHarborPage()" class="close">
<clr-icon shape="close"></clr-icon>
</button>
<h3 class="modal-title oidc-header-text"><span>{{'CONFIG.OIDC.OIDC_SETNAME' | translate}}</span>&nbsp;
</h3>
</div>
<div id="error-message">
<div class="alert alert-danger" role="alert" *ngIf="errorOpen">
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<clr-icon class="alert-icon" size="24" shape="exclamation-circle"></clr-icon>
</div>
<span class="alert-text">{{errorMessage}}</span>
</div>
</div>
<button type="button" class="close" aria-label="Close" (click)="emptyErrorMessage()">
<clr-icon aria-hidden="true" size="16" shape="close"></clr-icon>
</button>
</div>
</div>
<div class="modal-body">
<p class="body-message">{{'CONFIG.OIDC.OIDC_SETNAMECONTENT' | translate}}</p>
</div>
<br />
<div class="username-div">
<label for="oidcUsername" class="required">{{'CONFIG.OIDC.OIDC_USERNAME' | translate}}</label>
<label for="oidcUsername" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right">
<input name="oidcUsername" type="text" [formControl]="oidcUsername" required id="oidcUsername" size="40">
</label>
</div>
<div class="modal-footer">
<button class="btn btn-outline" type="button" (click)="backHarborPage()" id="cancelButton">{{'BUTTON.CANCEL' | translate }}</button>
<button class="btn btn-primary" id="saveButton" (click)="clickSaveBtn()" [disabled]="oidcUsername.invalid"
type="button">{{'BUTTON.SAVE' | translate }}</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
.modal {
background-color: rgb(80, 80, 80);
.body-message {
margin-top: 10px;
}
.modal-header {
.close {
margin-right: 0.2rem
}
}
.username-div {
display: flex;
justify-content: space-between;
width: 80%;
}
input {
width: 300px;
}
}
.oidc-header-text{
color:rgb(94, 94, 94);
}
.close-error {
padding-right:0;
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { OidcOnboardComponent } from './oidc-onboard.component';
describe('OidcOnboardComponent', () => {
let component: OidcOnboardComponent;
let fixture: ComponentFixture<OidcOnboardComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ OidcOnboardComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(OidcOnboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,43 @@
import { Router, ActivatedRoute } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { OidcOnboardService } from './oidc-onboard.service';
import { FormControl } from '@angular/forms';
import { errorHandler } from "../shared/shared.utils";
import { CommonRoutes } from '../shared/shared.const';
@Component({
selector: 'app-oidc-onboard',
templateUrl: './oidc-onboard.component.html',
styleUrls: ['./oidc-onboard.component.scss']
})
export class OidcOnboardComponent implements OnInit {
url: string;
errorMessage: string = '';
oidcUsername = new FormControl('');
errorOpen: boolean = false;
constructor(
private oidcOnboardService: OidcOnboardService,
private router: Router,
private route: ActivatedRoute,
) { }
ngOnInit() {
this.route.queryParams
.subscribe(params => {
this.oidcUsername.setValue(params["username"] || "");
});
}
clickSaveBtn(): void {
this.oidcOnboardService.oidcSave({ username: this.oidcUsername.value }).subscribe(res => { }
, error => {
this.errorMessage = errorHandler(error);
this.errorOpen = true;
});
}
emptyErrorMessage() {
this.errorOpen = false;
}
backHarborPage() {
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
}
}

View File

@ -0,0 +1,29 @@
// Copyright Project Harbor Authors
//
// 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.
import { NgModule } from '@angular/core';
import { OidcOnboardComponent } from './oidc-onboard.component';
import { SharedModule } from '../shared/shared.module';
import { OidcOnboardService } from './oidc-onboard.service';
@NgModule({
imports: [SharedModule],
declarations: [
OidcOnboardComponent,
],
providers: [OidcOnboardService],
exports: [
OidcOnboardComponent
]
})
export class OidcOnboardModule { }

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { OidcOnboardService } from './oidc-onboard.service';
describe('OidcOnboardService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: OidcOnboardService = TestBed.get(OidcOnboardService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { Http, URLSearchParams } from '@angular/http';
import { catchError } from 'rxjs/operators';
import { throwError as observableThrowError, Observable } from 'rxjs';
export const logEndpoint = "/c/oidc/onboard";
@Injectable()
export class OidcOnboardService {
constructor(private http: Http) { }
oidcSave(param): Observable<any> {
return this.http.post(logEndpoint, param).pipe(catchError(error => observableThrowError(error)));
}
}

View File

@ -0,0 +1,58 @@
// Copyright Project Harbor Authors
//
// 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.
import { Injectable } from '@angular/core';
import {
CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot,
CanActivateChild
} from '@angular/router';
import { AppConfigService } from '../../app-config.service';
import { UserPermissionService } from "@harbor/ui";
import { Observable, of } from 'rxjs';
import { CommonRoutes } from '../../shared/shared.const';
@Injectable()
export class OidcGuard implements CanActivate, CanActivateChild {
constructor(private appConfigService: AppConfigService, private router: Router, private userPermission: UserPermissionService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
// If user has logged in, should not login again
return new Observable((observer) => {
// If signout appended
let queryParams = route.queryParams;
this.appConfigService.load()
.subscribe(updatedConfig => {
if (updatedConfig.auth_mode === 'oidc_auth') {
return observer.next(true);
} else {
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
return observer.next(false);
}
}
, error => {
// Catch the error
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
console.error("Failed to load bootstrap options with error: ", error);
return observer.next(false);
});
});
}
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
return this.canActivate(route, state);
}
}

View File

@ -33,6 +33,7 @@ import { AuthCheckGuard } from "./route/auth-user-activate.service";
import { SignInGuard } from "./route/sign-in-guard-activate.service";
import { SystemAdminGuard } from "./route/system-admin-activate.service";
import { MemberGuard } from "./route/member-guard-activate.service";
import { OidcGuard } from "./route/oidc-guard-active.service";
import { LeavingRepositoryRouteDeactivate } from "./route/leaving-repository-deactivate.service";
import { PortValidatorDirective } from "./port.directive";
@ -140,6 +141,7 @@ const uiLibConfig: IServiceConfig = {
SignInGuard,
LeavingRepositoryRouteDeactivate,
MemberGuard,
OidcGuard,
MessageHandlerService,
StatisticHandler
]

View File

@ -696,7 +696,10 @@
"CLIENT_ID": "OIDC Client ID",
"CLIENTSECRET": "OIDC Client Secret",
"SCOPE": "OIDC Scope",
"OIDCSKIPCERTVERIFY": "OIDC Verify Cert"
"OIDCSKIPCERTVERIFY": "OIDC Verify Cert",
"OIDC_SETNAME": "Set OIDC Username",
"OIDC_SETNAMECONTENT": "You must create a Harbor username the first time when authenticating via a third party(OIDC).This will be used within Harbor to be associated with projects, roles, etc.",
"OIDC_USERNAME": "Username"
},
"SCANNING": {
"TRIGGER_SCAN_ALL_SUCCESS": "Trigger scan all successfully!",

View File

@ -695,7 +695,10 @@
"CLIENT_ID": "ID de cliente OIDC",
"CLIENTSECRET": "OIDC Client Secret",
"SCOPE": "OIDC Ámbito",
"OIDCSKIPCERTVERIFY": "OIDC Verify Cert"
"OIDCSKIPCERTVERIFY": "OIDC Verify Cert",
"OIDC_SETNAME": "Set OIDC nombre de usuario",
"OIDC_SETNAMECONTENT": "Usted debe crear un Harbor nombre de usuario la primera vez cuando la autenticación a través de un tercero (OIDC). Esta será usada en Harbor para ser asociados con proyectos, funciones, etc.",
"OIDC_USERNAME": "Usuario"
},
"SCANNING": {
"TRIGGER_SCAN_ALL_SUCCESS": "Trigger scan all successfully!",

View File

@ -660,7 +660,10 @@
"CLIENT_ID": "no d'identification du client OIDC",
"CLIENTSECRET": "OIDC Client Secret",
"SCOPE": "OIDC Scope",
"OIDCSKIPCERTVERIFY": "OIDC vérifier cert"
"OIDCSKIPCERTVERIFY": "OIDC vérifier cert",
"OIDC_SETNAME": "Ensemble OIDC nom d'utilisateur",
"OIDC_SETNAMECONTENT": "vous devez créer un Harbor identifiant la première fois lors de la vérification par une tierce partie (oidc). il sera utilisé au sein de port à être associés aux projets, des rôles, etc.",
"OIDC_USERNAME": "d'utilisateur"
},
"SCANNING": {
"TRIGGER_SCAN_ALL_SUCCESS": "Déclenchement d'analyse globale avec succès !",

View File

@ -689,7 +689,10 @@
"CLIENT_ID": "ID de cliente OIDC",
"CLIENTSECRET": "OIDC Client Secret",
"SCOPE": "Escopo OIDC",
"OIDCSKIPCERTVERIFY": "Verificar certificado OIDC"
"OIDCSKIPCERTVERIFY": "Verificar certificado OIDC",
"OIDC_SETNAME": "Definir o Utilizador OIDC",
"OIDC_SETNAMECONTENT": "Você deve Criar um Nome de usuário do Porto a primeira vez que autenticar através de um terceiro (OIDC). Isto será usado Dentro de Harbor para ser associado a projetos, papéis, etc.",
"OIDC_USERNAME": "Utilizador"
},
"SCANNING": {
"TRIGGER_SCAN_ALL_SUCCESS": "Disparo de análise geral efetuado com sucesso!",

View File

@ -694,7 +694,10 @@
"CLIENT_ID": "OIDC 客户端标识",
"CLIENTSECRET": "OIDC 客户端密码",
"SCOPE": "OIDC scope",
"OIDCSKIPCERTVERIFY": "OIDC 验证证书"
"OIDCSKIPCERTVERIFY": "OIDC 验证证书",
"OIDC_SETNAME": "设置OIDC用户名",
"OIDC_SETNAMECONTENT": "在通过第三方OIDC进行身份验证时您必须第一次创建一个Harbor用户名。这将在端口中用于与项目、角色等关联。",
"OIDC_USERNAME": "用户名"
},
"SCANNING": {
"TRIGGER_SCAN_ALL_SUCCESS": "启动扫描所有镜像任务成功!",