mirror of https://github.com/bitwarden/web.git
23 changed files with 637 additions and 111 deletions
@ -0,0 +1,284 @@
@@ -0,0 +1,284 @@
|
||||
<div class="page-header d-flex"> |
||||
<h1>{{'singleSignOn' | i18n}}</h1> |
||||
</div> |
||||
|
||||
<ng-container *ngIf="loading"> |
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> |
||||
<span class="sr-only">{{'loading' | i18n}}</span> |
||||
</ng-container> |
||||
|
||||
<form #form (ngSubmit)="submit()" [formGroup]="data" [appApiAction]="formPromise" *ngIf="!loading"> |
||||
<div class="form-group"> |
||||
<div class="form-check"> |
||||
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled"> |
||||
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="form-group"> |
||||
<label for="type">{{'type' | i18n}}</label> |
||||
<select class="form-control" id="type" formControlName="configType"> |
||||
<option value="0" disabled>{{'selectType' | i18n}}</option> |
||||
<option value="1">OpenID Connect</option> |
||||
<option value="2">SAML 2.0</option> |
||||
</select> |
||||
</div> |
||||
|
||||
<!-- OIDC --> |
||||
<div *ngIf="data.value.configType == 1"> |
||||
<div class="config-section"> |
||||
<h2>{{'openIdConnectConfig' | i18n}}</h2> |
||||
<div class="form-group"> |
||||
<label>{{'callbackPath' | i18n}}</label> |
||||
<div class="input-group"> |
||||
<input class="form-control" readonly [value]="callbackPath"> |
||||
<div class="input-group-append"> |
||||
<button type="button" class="btn btn-outline-secondary" |
||||
appA11yTitle="{{'copyValue' | i18n}}" |
||||
(click)="copy(data.value.callbackPath)"> |
||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'signedOutCallbackPath' | i18n}}</label> |
||||
<div class="input-group"> |
||||
<input class="form-control" readonly [value]="signedOutCallbackPath"> |
||||
<div class="input-group-append"> |
||||
<button type="button" class="btn btn-outline-secondary" |
||||
appA11yTitle="{{'copyValue' | i18n}}" |
||||
(click)="copy(data.value.signedOutCallbackPath)"> |
||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'authority' | i18n}}</label> |
||||
<input class="form-control" formControlName="authority"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'clientId' | i18n}}</label> |
||||
<input class="form-control" formControlName="clientId"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'clientSecret' | i18n}}</label> |
||||
<input class="form-control" formControlName="clientSecret"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'metadataAddress' | i18n}}</label> |
||||
<input class="form-control" formControlName="metadataAddress"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'oidcRedirectBehavior' | i18n}}</label> |
||||
<select class="form-control" formControlName="redirectBehavior"> |
||||
<option value="0">Redirect GET</option> |
||||
<option value="1">Form POST</option> |
||||
</select> |
||||
</div> |
||||
<div class="form-group"> |
||||
<div class="form-check"> |
||||
<input class="form-check-input" type="checkbox" id="getClaimsFromUserInfoEndpoint" |
||||
formControlName="getClaimsFromUserInfoEndpoint"> |
||||
<label class="form-check-label" for="getClaimsFromUserInfoEndpoint"> |
||||
{{'getClaimsFromUserInfoEndpoint' | i18n}} |
||||
</label> |
||||
</div> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'additionalScopes' | i18n}}</label> |
||||
<input class="form-control" formControlName="additionalScopes"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'additionalUserIdClaimTypes' | i18n}}</label> |
||||
<input class="form-control" formControlName="additionalUserIdClaimTypes"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'additionalEmailClaimTypes' | i18n}}</label> |
||||
<input class="form-control" formControlName="additionalEmailClaimTypes"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'additionalNameClaimTypes' | i18n}}</label> |
||||
<input class="form-control" formControlName="additionalNameClaimTypes"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'acrValues' | i18n}}</label> |
||||
<input class="form-control" formControlName="acrValues"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'expectedReturnAcrValue' | i18n}}</label> |
||||
<input class="form-control" formControlName="expectedReturnAcrValue"> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div *ngIf="data.value.configType == 2"> |
||||
<!-- SAML2 SP --> |
||||
<div class="config-section"> |
||||
<h2>{{'samlSpConfig' | i18n}}</h2> |
||||
<div class="form-group"> |
||||
<label>{{'spEntityId' | i18n}}</label> |
||||
<div class="input-group"> |
||||
<input class="form-control" readonly [value]="spEntityId" > |
||||
<div class="input-group-append"> |
||||
<button type="button" class="btn btn-outline-secondary" |
||||
appA11yTitle="{{'copyValue' | i18n}}" |
||||
(click)="copy(data.value.spEntityId)"> |
||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'spMetadataUrl' | i18n}}</label> |
||||
<div class="input-group"> |
||||
<input class="form-control" readonly [value]="spMetadataUrl"> |
||||
<div class="input-group-append"> |
||||
<button type="button" class="btn btn-outline-secondary" |
||||
appA11yTitle="{{'copyValue' | i18n}}" |
||||
(click)="launchUri(data.value.spMetadataUrl)"> |
||||
<i class="fa fa-lg fa-external-link" aria-hidden="true"></i> |
||||
</button> |
||||
<button type="button" class="btn btn-outline-secondary" |
||||
appA11yTitle="{{'copyValue' | i18n}}" |
||||
(click)="copy(data.value.spMetadataUrl)"> |
||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'spAcsUrl' | i18n}}</label> |
||||
<div class="input-group"> |
||||
<input class="form-control" readonly [value]="spAcsUrl"> |
||||
<div class="input-group-append"> |
||||
<button type="button" class="btn btn-outline-secondary" |
||||
appA11yTitle="{{'copyValue' | i18n}}" |
||||
(click)="copy(data.value.spAcsUrl)"> |
||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'spNameIdFormat' | i18n}}</label> |
||||
<select class="form-control" formControlName="spNameIdFormat"> |
||||
<option value="0">Not Configured</option> |
||||
<option value="1">Unspecified</option> |
||||
<option value="2">Email Address</option> |
||||
<option value="3">X.509 Subject Name</option> |
||||
<option value="4">Windows Domain Qualified Name</option> |
||||
<option value="5">Kerberos Principal Name</option> |
||||
<option value="6">Entity Identifier</option> |
||||
<option value="7">Persistent</option> |
||||
<option value="8">Transient</option> |
||||
</select> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'spOutboundSigningAlgorithm' | i18n}}</label> |
||||
<select class="form-control" formControlName="spOutboundSigningAlgorithm"> |
||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{o}}</option> |
||||
</select> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'spSigningBehavior' | i18n}}</label> |
||||
<select class="form-control" formControlName="spSigningBehavior"> |
||||
<option value="0">If IdP Wants Authn Requests Signed</option> |
||||
<option value="1">Always</option> |
||||
<option value="3">Never</option> |
||||
</select> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'spMinIncomingSigningAlgorithm' | i18n}}</label> |
||||
<select class="form-control" formControlName="spMinIncomingSigningAlgorithm"> |
||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{o}}</option> |
||||
</select> |
||||
</div> |
||||
<div class="form-group"> |
||||
<div class="form-check"> |
||||
<input class="form-check-input" type="checkbox" id="spWantAssertionsSigned" formControlName="spWantAssertionsSigned"> |
||||
<label class="form-check-label" for="spWantAssertionsSigned">{{'spWantAssertionsSigned' | i18n}}</label> |
||||
</div> |
||||
</div> |
||||
<div class="form-group"> |
||||
<div class="form-check"> |
||||
<input class="form-check-input" type="checkbox" id="spValidateCertificates" formControlName="spValidateCertificates"> |
||||
<label class="form-check-label" for="spValidateCertificates">{{'spValidateCertificates' | i18n}}</label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<!-- SAML2 IDP --> |
||||
<div class="config-section"> |
||||
<h2>{{'samlIdpConfig' | i18n}}</h2> |
||||
|
||||
<div class="form-group"> |
||||
<label>{{'idpEntityId' | i18n}}</label> |
||||
<input class="form-control" formControlName="idpEntityId"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'idpBindingType' | i18n}}</label> |
||||
<select class="form-control" formControlName="idpBindingType"> |
||||
<option value="1">Redirect</option> |
||||
<option value="2">HTTP POST</option> |
||||
<option value="4">Artifact</option> |
||||
</select> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'idpSingleSignOnServiceUrl' | i18n}}</label> |
||||
<input class="form-control" formControlName="idpSingleSignOnServiceUrl"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'idpSingleLogoutServiceUrl' | i18n}}</label> |
||||
<input class="form-control" formControlName="idpSingleLogoutServiceUrl"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'idpArtifactResolutionServiceUrl' | i18n}}</label> |
||||
<input class="form-control" formControlName="idpArtifactResolutionServiceUrl"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'idpX509PublicCert' | i18n}}</label> |
||||
<textarea formControlName="idpX509PublicCert" class="form-control form-control-sm text-monospace" rows="6"></textarea> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>{{'idpOutboundSigningAlgorithm' | i18n}}</label> |
||||
<select class="form-control" formControlName="idpOutboundSigningAlgorithm"> |
||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{o}}</option> |
||||
</select> |
||||
</div> |
||||
<div class="form-group"> |
||||
<div class="form-check"> |
||||
<input class="form-check-input" type="checkbox" id="idpAllowUnsolicitedAuthnResponse" |
||||
formControlName="idpAllowUnsolicitedAuthnResponse"> |
||||
<label class="form-check-label" for="idpAllowUnsolicitedAuthnResponse"> |
||||
{{'idpAllowUnsolicitedAuthnResponse' | i18n}} |
||||
</label> |
||||
</div> |
||||
</div> |
||||
<div class="form-group"> |
||||
<div class="form-check"> |
||||
<input class="form-check-input" type="checkbox" id="idpDisableOutboundLogoutRequests" |
||||
formControlName="idpDisableOutboundLogoutRequests"> |
||||
<label class="form-check-label" for="idpDisableOutboundLogoutRequests"> |
||||
{{'idpDisableOutboundLogoutRequests' | i18n}} |
||||
</label> |
||||
</div> |
||||
</div> |
||||
<div class="form-group"> |
||||
<div class="form-check"> |
||||
<input class="form-check-input" type="checkbox" id="idpWantAuthnRequestsSigned" |
||||
formControlName="idpWantAuthnRequestsSigned"> |
||||
<label class="form-check-label" for="idpWantAuthnRequestsSigned"> |
||||
{{'idpWantAuthnRequestsSigned' | i18n}} |
||||
</label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"> |
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> |
||||
<span>{{'save' | i18n}}</span> |
||||
</button> |
||||
</form> |
||||
@ -0,0 +1,121 @@
@@ -0,0 +1,121 @@
|
||||
import { |
||||
Component, |
||||
OnInit, |
||||
} from '@angular/core'; |
||||
import { FormBuilder } from '@angular/forms'; |
||||
import { ActivatedRoute } from '@angular/router'; |
||||
|
||||
import { ApiService } from 'jslib-common/abstractions/api.service'; |
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service'; |
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; |
||||
import { OrganizationSsoRequest } from 'jslib-common/models/request/organization/organizationSsoRequest'; |
||||
|
||||
@Component({ |
||||
selector: 'app-org-manage-sso', |
||||
templateUrl: 'sso.component.html', |
||||
}) |
||||
export class SsoComponent implements OnInit { |
||||
|
||||
samlSigningAlgorithms = [ |
||||
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', |
||||
'http://www.w3.org/2000/09/xmldsig#rsa-sha384', |
||||
'http://www.w3.org/2000/09/xmldsig#rsa-sha512', |
||||
'http://www.w3.org/2000/09/xmldsig#rsa-sha1', |
||||
]; |
||||
|
||||
loading = true; |
||||
organizationId: string; |
||||
formPromise: Promise<any>; |
||||
|
||||
callbackPath: string; |
||||
signedOutCallbackPath: string; |
||||
spEntityId: string; |
||||
spMetadataUrl: string; |
||||
spAcsUrl: string; |
||||
|
||||
enabled = this.fb.control(false); |
||||
data = this.fb.group({ |
||||
configType: [], |
||||
|
||||
// OpenId
|
||||
authority: [], |
||||
clientId: [], |
||||
clientSecret: [], |
||||
metadataAddress: [], |
||||
redirectBehavior: [], |
||||
getClaimsFromUserInfoEndpoint: [], |
||||
additionalScopes: [], |
||||
additionalUserIdClaimTypes: [], |
||||
additionalEmailClaimTypes: [], |
||||
additionalNameClaimTypes: [], |
||||
acrValues: [], |
||||
expectedReturnAcrValue: [], |
||||
|
||||
// SAML
|
||||
spNameIdFormat: [], |
||||
spOutboundSigningAlgorithm: [], |
||||
spSigningBehavior: [], |
||||
spMinIncomingSigningAlgorithm: [], |
||||
spWantAssertionsSigned: [], |
||||
spValidateCertificates: [], |
||||
|
||||
idpEntityId: [], |
||||
idpBindingType: [], |
||||
idpSingleSignOnServiceUrl: [], |
||||
idpSingleLogoutServiceUrl: [], |
||||
idpArtifactResolutionServiceUrl: [], |
||||
idpX509PublicCert: [], |
||||
idpOutboundSigningAlgorithm: [], |
||||
idpAllowUnsolicitedAuthnResponse: [], |
||||
idpDisableOutboundLogoutRequests: [], |
||||
idpWantAuthnRequestsSigned: [], |
||||
}); |
||||
|
||||
constructor(private fb: FormBuilder, private route: ActivatedRoute, private apiService: ApiService, |
||||
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService) { } |
||||
|
||||
async ngOnInit() { |
||||
this.route.parent.parent.params.subscribe(async params => { |
||||
this.organizationId = params.organizationId; |
||||
await this.load(); |
||||
}); |
||||
} |
||||
|
||||
async load() { |
||||
const ssoSettings = await this.apiService.getOrganizationSso(this.organizationId); |
||||
|
||||
this.data.patchValue(ssoSettings.data); |
||||
this.enabled.setValue(ssoSettings.enabled); |
||||
|
||||
this.callbackPath = ssoSettings.urls.callbackPath; |
||||
this.signedOutCallbackPath = ssoSettings.urls.signedOutCallbackPath; |
||||
this.spEntityId = ssoSettings.urls.spEntityId; |
||||
this.spMetadataUrl = ssoSettings.urls.spMetadataUrl; |
||||
this.spAcsUrl = ssoSettings.urls.spAcsUrl; |
||||
|
||||
this.loading = false; |
||||
} |
||||
|
||||
copy(value: string) { |
||||
this.platformUtilsService.copyToClipboard(value); |
||||
} |
||||
|
||||
launchUri(url: string) { |
||||
this.platformUtilsService.launchUri(url); |
||||
} |
||||
|
||||
async submit() { |
||||
const request = new OrganizationSsoRequest(); |
||||
request.enabled = this.enabled.value; |
||||
request.data = this.data.value; |
||||
|
||||
this.formPromise = this.apiService.postOrganizationSso(this.organizationId, request); |
||||
|
||||
const response = await this.formPromise; |
||||
this.data.patchValue(response.data); |
||||
this.enabled.setValue(response.enabled); |
||||
|
||||
this.formPromise = null; |
||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('ssoSettingsSaved')); |
||||
} |
||||
} |
||||
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
import { NgModule } from '@angular/core'; |
||||
import { RouterModule, Routes } from '@angular/router'; |
||||
|
||||
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service'; |
||||
|
||||
import { Permissions } from 'jslib-common/enums/permissions'; |
||||
|
||||
import { OrganizationLayoutComponent } from 'src/app/layouts/organization-layout.component'; |
||||
import { ManageComponent } from 'src/app/organizations/manage/manage.component'; |
||||
import { OrganizationGuardService } from 'src/app/services/organization-guard.service'; |
||||
import { OrganizationTypeGuardService } from 'src/app/services/organization-type-guard.service'; |
||||
|
||||
import { SsoComponent } from './manage/sso.component'; |
||||
|
||||
const routes: Routes = [ |
||||
{ |
||||
path: 'organizations/:organizationId', |
||||
component: OrganizationLayoutComponent, |
||||
canActivate: [AuthGuardService, OrganizationGuardService], |
||||
children: [ |
||||
{ |
||||
path: 'manage', |
||||
component: ManageComponent, |
||||
canActivate: [OrganizationTypeGuardService], |
||||
data: { |
||||
permissions: [ |
||||
Permissions.ManageAssignedCollections, |
||||
Permissions.ManageAllCollections, |
||||
Permissions.AccessEventLogs, |
||||
Permissions.ManageGroups, |
||||
Permissions.ManageUsers, |
||||
Permissions.ManagePolicies, |
||||
Permissions.ManageSso, |
||||
], |
||||
}, |
||||
children: [ |
||||
{ |
||||
path: 'sso', |
||||
component: SsoComponent, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
]; |
||||
|
||||
@NgModule({ |
||||
imports: [RouterModule.forChild(routes)], |
||||
exports: [RouterModule], |
||||
}) |
||||
export class OrganizationsRoutingModule { } |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
import { CommonModule } from '@angular/common'; |
||||
import { NgModule } from '@angular/core'; |
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; |
||||
|
||||
import { OssModule } from 'src/app/oss.module'; |
||||
|
||||
import { SsoComponent } from './manage/sso.component'; |
||||
import { OrganizationsRoutingModule } from './organizations-routing.module'; |
||||
|
||||
@NgModule({ |
||||
imports: [ |
||||
CommonModule, |
||||
FormsModule, |
||||
ReactiveFormsModule, |
||||
OssModule, |
||||
OrganizationsRoutingModule, |
||||
], |
||||
declarations: [ |
||||
SsoComponent, |
||||
], |
||||
}) |
||||
export class OrganizationsModule {} |
||||
@ -1,7 +1,6 @@
@@ -1,7 +1,6 @@
|
||||
{ |
||||
"urls": { |
||||
"icons": "https://icons.qa.bitwarden.pw", |
||||
"notifications": "https://notifications.qa.bitwarden.pw", |
||||
"enterprise": "https://portal.qa.bitwarden.pw" |
||||
"notifications": "https://notifications.qa.bitwarden.pw" |
||||
} |
||||
} |
||||
|
||||
@ -1 +1 @@
@@ -1 +1 @@
|
||||
Subproject commit 91c5393ae7a84e9f4d90391d072cae56e7a3ff41 |
||||
Subproject commit bfa9a1e1bc05fe96c121eb4709729e85dfb3b008 |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core'; |
||||
import { RouterModule, Routes } from '@angular/router'; |
||||
|
||||
const routes: Routes = [ |
||||
{ path: '**', redirectTo: '' }, |
||||
]; |
||||
|
||||
@NgModule({ |
||||
imports: [RouterModule.forChild(routes)], |
||||
exports: [RouterModule], |
||||
}) |
||||
export class WildcardRoutingModule { } |
||||
Loading…
Reference in new issue