mirror of
https://github.com/goharbor/harbor
synced 2025-04-06 18:31:50 +00:00
oidclogout (#21718)
* oidclogout enable oidc session logout 1, give the option of logging out user session from OIDC provider. 2, try best to log out the user offline session if the offline_access in the scope. Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
816667c794
commit
5960bc8fb2
|
@ -9065,6 +9065,9 @@ definitions:
|
|||
oidc_extra_redirect_parms:
|
||||
$ref: '#/definitions/StringConfigItem'
|
||||
description: Extra parameters to add when redirect request to OIDC provider
|
||||
oidc_logout:
|
||||
$ref: '#/definitions/BoolConfigItem'
|
||||
description: Extra parameters to logout user session from the OIDC provider
|
||||
robot_token_duration:
|
||||
$ref: '#/definitions/IntegerConfigItem'
|
||||
description: The robot account token duration in days
|
||||
|
@ -9339,6 +9342,11 @@ definitions:
|
|||
description: Extra parameters to add when redirect request to OIDC provider
|
||||
x-omitempty: true
|
||||
x-isnullable: true
|
||||
oidc_logout:
|
||||
type: boolean
|
||||
description: Logout OIDC user session
|
||||
x-omitempty: true
|
||||
x-isnullable: true
|
||||
robot_token_duration:
|
||||
type: integer
|
||||
description: The robot account token duration in days
|
||||
|
|
|
@ -119,6 +119,7 @@ const (
|
|||
OIDCExtraRedirectParms = "oidc_extra_redirect_parms"
|
||||
OIDCScope = "oidc_scope"
|
||||
OIDCUserClaim = "oidc_user_claim"
|
||||
OIDCLogout = "oidc_logout"
|
||||
|
||||
CfgDriverDB = "db"
|
||||
NewHarborAdminName = "admin@harbor.local"
|
||||
|
@ -151,6 +152,7 @@ const (
|
|||
|
||||
OIDCCallbackPath = "/c/oidc/callback"
|
||||
OIDCLoginPath = "/c/oidc/login"
|
||||
OIDCLoginoutPath = "/c/oidc/logout"
|
||||
|
||||
AuthProxyRedirectPath = "/c/authproxy/redirect"
|
||||
|
||||
|
|
|
@ -108,8 +108,36 @@ func (cc *CommonController) Login() {
|
|||
cc.PopulateUserSession(*user)
|
||||
}
|
||||
|
||||
// LogOut Habor UI
|
||||
// LogOut Harbor UI
|
||||
func (cc *CommonController) LogOut() {
|
||||
// redirect for OIDC logout.
|
||||
securityCtx, ok := security.FromContext(cc.Context())
|
||||
if !ok {
|
||||
log.Error("Failed to get security context")
|
||||
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
principal := securityCtx.GetUsername()
|
||||
if principal != "" {
|
||||
if redirectForOIDC(cc.Ctx.Request.Context(), principal) {
|
||||
ep, err := config.ExtEndpoint()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get the external endpoint, error: %v", err)
|
||||
cc.CustomAbort(http.StatusUnauthorized, "")
|
||||
}
|
||||
url := strings.TrimSuffix(ep, "/") + common.OIDCLoginoutPath
|
||||
log.Debugf("Redirect user %s to logout page of OIDC provider", principal)
|
||||
// Return a json to UI with status code 403, as it cannot handle status 302
|
||||
cc.Ctx.Output.Status = http.StatusForbidden
|
||||
err = cc.Ctx.Output.JSON(struct {
|
||||
Location string `json:"redirect_location"`
|
||||
}{url}, false, false)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to write json to response body, error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := cc.DestroySession(); err != nil {
|
||||
log.Errorf("Error occurred in LogOut: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
|
|
|
@ -16,9 +16,11 @@ package controllers
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
|
@ -244,6 +246,84 @@ func (oc *OIDCController) Callback() {
|
|||
}
|
||||
}
|
||||
|
||||
func (oc *OIDCController) RedirectLogout() {
|
||||
sessionData := oc.GetSession(tokenKey)
|
||||
ctx := oc.Ctx.Request.Context()
|
||||
if err := oc.DestroySession(); err != nil {
|
||||
log.Errorf("Error occurred in LogOut: %v", err)
|
||||
oc.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
if sessionData == nil {
|
||||
log.Warningf("OIDC session token not found.")
|
||||
oc.Controller.Redirect("/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
oidcSettings, err := config.OIDCSetting(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get OIDC settings: %v", err)
|
||||
oc.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
if oidcSettings == nil {
|
||||
log.Error("OIDC settings is missing.")
|
||||
oc.SendInternalServerError(fmt.Errorf("OIDC settings is missing"))
|
||||
return
|
||||
}
|
||||
if !oidcSettings.Logout {
|
||||
oc.Controller.Redirect("/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
tk, ok := sessionData.([]byte)
|
||||
if !ok {
|
||||
log.Error("Invalid OIDC session data format.")
|
||||
oc.SendInternalServerError(fmt.Errorf("invalid OIDC session data format"))
|
||||
return
|
||||
}
|
||||
token := oidc.Token{}
|
||||
if err := json.Unmarshal(tk, &token); err != nil {
|
||||
log.Errorf("Error occurred in Unmarshal: %v", err)
|
||||
oc.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
if token.RefreshToken != "" {
|
||||
sessionType, err := getSessionType(token.RefreshToken)
|
||||
if err == nil {
|
||||
// If the session is offline, try best to revoke the refresh token.
|
||||
if strings.ToLower(sessionType) == "offline" && oidc.EndpointsClaims.RevokeURL != "" {
|
||||
if err := oidc.RevokeOIDCRefreshToken(oidc.EndpointsClaims.RevokeURL, token.RefreshToken, oidcSettings.ClientID, oidcSettings.ClientSecret, oidcSettings.VerifyCert); err != nil {
|
||||
log.Warningf("Failed to revoke the offline session: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if token.RawIDToken == "" {
|
||||
log.Warning("Empty ID token for offline session.")
|
||||
oc.Controller.Redirect("/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
if oidc.EndpointsClaims.EndSessionURL == "" {
|
||||
log.Warning("Unable to logout OIDC session since the 'end_session_point' is not set.")
|
||||
oc.Controller.Redirect("/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
endSessionURL := oidc.EndpointsClaims.EndSessionURL
|
||||
baseURL, err := config.ExtEndpoint()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get external endpoint: %v", err)
|
||||
oc.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
logoutURL := fmt.Sprintf(
|
||||
"%s?id_token_hint=%s&post_logout_redirect_uri=%s",
|
||||
endSessionURL,
|
||||
url.QueryEscape(token.RawIDToken),
|
||||
url.QueryEscape(baseURL),
|
||||
)
|
||||
log.Info("Redirect user to logout page of OIDC provider:", logoutURL)
|
||||
oc.Controller.Redirect(logoutURL, http.StatusFound)
|
||||
}
|
||||
|
||||
func userOnboard(ctx context.Context, oc *OIDCController, info *oidc.UserInfo, username string, tokenBytes []byte) (*models.User, bool) {
|
||||
s, t, err := secretAndToken(tokenBytes)
|
||||
if err != nil {
|
||||
|
@ -338,3 +418,24 @@ func secretAndToken(tokenBytes []byte) (string, string, error) {
|
|||
}
|
||||
return secret, token, nil
|
||||
}
|
||||
|
||||
// getSessionType determines if the session is offline by decoding the refresh token or not
|
||||
func getSessionType(refreshToken string) (string, error) {
|
||||
parts := strings.Split(refreshToken, ".")
|
||||
if len(parts) != 3 {
|
||||
return "", errors.Errorf("invalid refresh token")
|
||||
}
|
||||
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return "", errors.Errorf("failed to decode refresh token: %v", err)
|
||||
}
|
||||
var claims map[string]interface{}
|
||||
if err := json.Unmarshal(payload, &claims); err != nil {
|
||||
return "", errors.Errorf("failed to unmarshal refresh token: %v", err)
|
||||
}
|
||||
typ, ok := claims["typ"].(string)
|
||||
if !ok {
|
||||
return "", errors.New("missing 'typ' claim in refresh token")
|
||||
}
|
||||
return typ, nil
|
||||
}
|
||||
|
|
47
src/core/controllers/oidc_test.go
Normal file
47
src/core/controllers/oidc_test.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetSessionType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
refreshToken string
|
||||
expectedType string
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "Valid",
|
||||
refreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXAiOiJvZmZsaW5lIn0.d9fcdba7c10fc1263bf682947afabaecf3496070cd2d5a5e7b3c79dbf1545c1f",
|
||||
expectedType: "offline",
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid",
|
||||
refreshToken: "invalidToken",
|
||||
expectedType: "",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "Missing 'typ' claim",
|
||||
refreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzI1NiJ9.d9fcdba7c10fc1263bf682947afabaecf3496070cd2d5a5e7b3c79dbf1545c1f",
|
||||
expectedType: "",
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
typ, err := getSessionType(tt.refreshToken)
|
||||
if tt.expectedError {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tt.expectedType, typ)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedType, typ)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -147,6 +147,7 @@ var (
|
|||
{Name: common.OIDCVerifyCert, Scope: UserScope, Group: OIDCGroup, DefaultValue: "true", ItemType: &BoolType{}, Description: `Verify the OIDC provider's certificate'`},
|
||||
{Name: common.OIDCAutoOnboard, Scope: UserScope, Group: OIDCGroup, DefaultValue: "false", ItemType: &BoolType{}, Description: `Auto onboard the OIDC user`},
|
||||
{Name: common.OIDCExtraRedirectParms, Scope: UserScope, Group: OIDCGroup, DefaultValue: "{}", ItemType: &StringToStringMapType{}, Description: `Extra parameters to add when redirect request to OIDC provider`},
|
||||
{Name: common.OIDCLogout, Scope: UserScope, Group: OIDCGroup, DefaultValue: "false", ItemType: &BoolType{}, Description: `Enable OIDC logout to log out user session from the identity provider.`},
|
||||
|
||||
{Name: common.WithTrivy, Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_TRIVY", DefaultValue: "false", ItemType: &BoolType{}, Editable: true},
|
||||
// the unit of expiration is days
|
||||
|
|
|
@ -44,6 +44,7 @@ type OIDCSetting struct {
|
|||
Scope []string `json:"scope"`
|
||||
UserClaim string `json:"user_claim"`
|
||||
ExtraRedirectParms map[string]string `json:"extra_redirect_parms"`
|
||||
Logout bool `json:"logout"`
|
||||
}
|
||||
|
||||
// QuotaSetting wraps the settings for Quota
|
||||
|
|
|
@ -257,6 +257,7 @@ func TestOIDCSetting(t *testing.T) {
|
|||
common.OIDCCLientID: "client",
|
||||
common.OIDCClientSecret: "secret",
|
||||
common.ExtEndpoint: "https://harbor.test",
|
||||
common.OIDCLogout: "false",
|
||||
}
|
||||
InitWithSettings(m)
|
||||
v, e := OIDCSetting(orm.Context())
|
||||
|
@ -271,6 +272,7 @@ func TestOIDCSetting(t *testing.T) {
|
|||
assert.Equal(t, "https://harbor.test/c/oidc/callback", v.RedirectURL)
|
||||
assert.ElementsMatch(t, []string{"openid", "profile"}, v.Scope)
|
||||
assert.Equal(t, "username", v.UserClaim)
|
||||
assert.False(t, v.Logout)
|
||||
}
|
||||
|
||||
func TestSplitAndTrim(t *testing.T) {
|
||||
|
|
|
@ -177,6 +177,7 @@ func OIDCSetting(ctx context.Context) (*cfgModels.OIDCSetting, error) {
|
|||
Scope: scope,
|
||||
UserClaim: mgr.Get(ctx, common.OIDCUserClaim).GetString(),
|
||||
ExtraRedirectParms: mgr.Get(ctx, common.OIDCExtraRedirectParms).GetStringToStringMap(),
|
||||
Logout: mgr.Get(ctx, common.OIDCLogout).GetBool(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -15,10 +15,12 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -32,6 +34,7 @@ import (
|
|||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
cfgModels "github.com/goharbor/harbor/src/lib/config/models"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup"
|
||||
|
@ -54,6 +57,11 @@ type providerHelper struct {
|
|||
creationTime time.Time
|
||||
}
|
||||
|
||||
var EndpointsClaims struct {
|
||||
EndSessionURL string `json:"end_session_endpoint"`
|
||||
RevokeURL string `json:"revocation_endpoint"`
|
||||
}
|
||||
|
||||
func (p *providerHelper) get(ctx context.Context) (*gooidc.Provider, error) {
|
||||
if p.instance.Load() != nil {
|
||||
if time.Since(p.creationTime) > 3*time.Second {
|
||||
|
@ -85,6 +93,10 @@ func (p *providerHelper) create(ctx context.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to create OIDC provider, error: %v", err)
|
||||
}
|
||||
err = provider.Claims(&EndpointsClaims)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.instance.Store(provider)
|
||||
p.creationTime = time.Now()
|
||||
return nil
|
||||
|
@ -485,3 +497,33 @@ func TestEndpoint(conn Conn) error {
|
|||
_, err := gooidc.NewProvider(ctx, conn.URL)
|
||||
return err
|
||||
}
|
||||
|
||||
// RevokeOIDCRefreshToken revokes an offline session using the refresh token
|
||||
func RevokeOIDCRefreshToken(revokeURL, refreshToken, clientID, clientSecret string, verifyCert bool) error {
|
||||
data := url.Values{}
|
||||
data.Set("token", refreshToken)
|
||||
data.Set("token_type_hint", "refresh_token")
|
||||
req, err := http.NewRequest("POST", revokeURL, bytes.NewBufferString(data.Encode()))
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to create request: %v", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.SetBasicAuth(clientID, clientSecret)
|
||||
var client *http.Client
|
||||
if !verifyCert {
|
||||
client = &http.Client{
|
||||
Transport: insecureTransport,
|
||||
}
|
||||
} else {
|
||||
client = &http.Client{}
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Errorf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 300 || resp.StatusCode < 200 {
|
||||
return errors.Errorf("logout failed, status: %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -966,6 +966,32 @@
|
|||
[(ngModel)]="currentConfig.oidc_auto_onboard.value" />
|
||||
</clr-checkbox-wrapper>
|
||||
</clr-checkbox-container>
|
||||
<clr-checkbox-container>
|
||||
<label for="oidcLogout"
|
||||
>{{ 'CONFIG.OIDC.OIDC_LOGOUT' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen>
|
||||
<span>{{ 'TOOLTIP.OIDC_LOGOUT' | translate }}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<clr-checkbox-wrapper>
|
||||
<input
|
||||
type="checkbox"
|
||||
clrCheckbox
|
||||
name="oidcLogout"
|
||||
id="oidcLogout"
|
||||
[disabled]="disabled(currentConfig.oidc_logout)"
|
||||
[(ngModel)]="currentConfig.oidc_logout.value" />
|
||||
</clr-checkbox-wrapper>
|
||||
</clr-checkbox-container>
|
||||
<clr-input-container>
|
||||
<label for="oidcUserClaim"
|
||||
>{{ 'CONFIG.OIDC.USER_CLAIM' | translate }}
|
||||
|
|
|
@ -105,6 +105,7 @@ export class Configuration {
|
|||
oidc_auto_onboard?: BoolValueItem;
|
||||
oidc_scope?: StringValueItem;
|
||||
oidc_user_claim?: StringValueItem;
|
||||
oidc_logout?: BoolValueItem;
|
||||
count_per_project: NumberValueItem;
|
||||
storage_per_project: NumberValueItem;
|
||||
cfg_expiration: NumberValueItem;
|
||||
|
@ -187,6 +188,7 @@ export class Configuration {
|
|||
this.oidc_admin_group = new StringValueItem('', true);
|
||||
this.oidc_group_filter = new StringValueItem('', true);
|
||||
this.oidc_user_claim = new StringValueItem('', true);
|
||||
this.oidc_logout = new BoolValueItem(false, true);
|
||||
this.count_per_project = new NumberValueItem(-1, true);
|
||||
this.storage_per_project = new NumberValueItem(-1, true);
|
||||
this.audit_log_forward_endpoint = new StringValueItem('', true);
|
||||
|
|
|
@ -24,6 +24,7 @@ import { MessageHandlerService } from '../../services/message-handler.service';
|
|||
import { SkinableConfig } from '../../../services/skinable-config.service';
|
||||
import {
|
||||
CommonRoutes,
|
||||
CONFIG_AUTH_MODE,
|
||||
DATETIME_RENDERINGS,
|
||||
DatetimeRendering,
|
||||
DEFAULT_DATETIME_RENDERING_LOCALSTORAGE_KEY,
|
||||
|
@ -44,6 +45,7 @@ import { ClrCommonStrings } from '@clr/angular/utils/i18n/common-strings.interfa
|
|||
import { map } from 'rxjs/operators';
|
||||
import { forkJoin, Observable } from 'rxjs';
|
||||
import { ClrCommonStringsService } from '@clr/angular';
|
||||
import { signInStatusError } from '../../../account/sign-in/sign-in.component';
|
||||
|
||||
@Component({
|
||||
selector: 'navigator',
|
||||
|
@ -60,6 +62,7 @@ export class NavigatorComponent implements OnInit {
|
|||
selectedDatetimeRendering: DatetimeRendering = DefaultDatetimeRendering;
|
||||
appTitle: string = 'APP_TITLE.HARBOR';
|
||||
customStyle: CustomStyle;
|
||||
isCoreServiceAvailable: boolean = true;
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private router: Router,
|
||||
|
@ -124,7 +127,13 @@ export class NavigatorComponent implements OnInit {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public get isOidcLoginMode(): boolean {
|
||||
return (
|
||||
this.appConfigService.getConfig() &&
|
||||
this.appConfigService.getConfig().auth_mode ===
|
||||
CONFIG_AUTH_MODE.OIDC_AUTH
|
||||
);
|
||||
}
|
||||
public get currentDatetimeRendering(): string {
|
||||
return DATETIME_RENDERINGS[this.selectedDatetimeRendering];
|
||||
}
|
||||
|
@ -199,16 +208,43 @@ export class NavigatorComponent implements OnInit {
|
|||
|
||||
// Log out system
|
||||
logOut(): void {
|
||||
// Naviagte to the sign in router-guard
|
||||
// Appending 'signout' means destroy session cache
|
||||
let signout = true;
|
||||
let redirect_url = this.location.pathname;
|
||||
let navigatorExtra: NavigationExtras = {
|
||||
queryParams: { signout, redirect_url },
|
||||
};
|
||||
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], navigatorExtra);
|
||||
// Confirm search result panel is close
|
||||
this.searchTrigger.closeSearch(true);
|
||||
// Call the service to send out the http request
|
||||
this.session.signOff().subscribe(
|
||||
() => {
|
||||
// Naviagte to the sign in router-guard
|
||||
// Appending 'signout' means destroy session cache
|
||||
let signout = true;
|
||||
let redirect_url = this.location.pathname;
|
||||
let navigatorExtra: NavigationExtras = {
|
||||
queryParams: { signout, redirect_url },
|
||||
};
|
||||
this.router.navigate(
|
||||
[CommonRoutes.EMBEDDED_SIGN_IN],
|
||||
navigatorExtra
|
||||
);
|
||||
// Confirm search result panel is close
|
||||
this.searchTrigger.closeSearch(true);
|
||||
},
|
||||
error => {
|
||||
// 403 oidc logout no body;
|
||||
if (this.isOidcLoginMode && error && error.status === 403) {
|
||||
try {
|
||||
let redirect_location = '';
|
||||
redirect_location =
|
||||
error.error && error.error.redirect_location
|
||||
? error.error.redirect_location
|
||||
: JSON.parse(error.error).redirect_location;
|
||||
window.location.href = redirect_location;
|
||||
return;
|
||||
} catch (error) {}
|
||||
}
|
||||
// core service is not available for error code 5xx
|
||||
if (error && /5[0-9][0-9]/.test(error.status)) {
|
||||
this.isCoreServiceAvailable = false;
|
||||
}
|
||||
this.handleError(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Switch languages
|
||||
|
@ -263,4 +299,13 @@ export class NavigatorComponent implements OnInit {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// General error handler
|
||||
handleError(error: any) {
|
||||
// Set error status
|
||||
let message = error.status
|
||||
? error.status + ':' + error.statusText
|
||||
: error;
|
||||
console.error('An error occurred when signing out:', message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ export class SessionService {
|
|||
// Destroy current session cache
|
||||
// this.currentUser = null;
|
||||
}), // Nothing returned
|
||||
catchError(error => this.handleError(error))
|
||||
catchError(error => observableThrowError(error))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,8 @@
|
|||
"OIDC_VERIFYCERT": "Box deaktivieren, falls der OIDC Server mit selbstsignierten Zertifikaten betrieben wird.",
|
||||
"OIDC_AUTOONBOARD": "Überspringe die Startseite, hierdurch können Nutzer ihren Nutzernamen nicht ändern. Der Nutzername wird aus dem ID Token übernommen.",
|
||||
"OIDC_USER_CLAIM": "Der Name des Claims im ID Token, aus dem der Nutzername stammt. Falls das Feld leer ist, wird 'name' verwendet.",
|
||||
"NEW_SECRET": "Das Secret muss länger als 8 Zeichen sein, mit jeweils einem Groß-, einem Kleinbuchstaben und einer Ziffer."
|
||||
"NEW_SECRET": "Das Secret muss länger als 8 Zeichen sein, mit jeweils einem Groß-, einem Kleinbuchstaben und einer Ziffer.",
|
||||
"OIDC_LOGOUT": "Logs the user out of their current session with the Identity Provider."
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "Aktuelles Passwort eingeben",
|
||||
|
@ -985,7 +986,8 @@
|
|||
"OIDC_ADMIN_GROUP": "OIDC Administratorengruppe",
|
||||
"OIDC_ADMIN_GROUP_INFO": "Spezifiziere den Namen einer OIDC Administratorengruppe. Alle Mitglieder dieser Gruppe haben in Harbor administrative Berechtigungen. Falls dies nicht gewünscht ist, kann das Feld leer gelassen werden.",
|
||||
"OIDC_GROUP_FILTER": "OIDC Group Filter",
|
||||
"OIDC_GROUP_FILTER_INFO": "Filter OIDC groups who match the provided regular expression.Keep it blank to match all the groups."
|
||||
"OIDC_GROUP_FILTER_INFO": "Filter OIDC groups who match the provided regular expression.Keep it blank to match all the groups.",
|
||||
"OIDC_LOGOUT": "OIDC Session Logout",
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "Alle Scans erfolgreich zum Anhalten aufgefordert!",
|
||||
|
|
|
@ -106,7 +106,8 @@
|
|||
"OIDC_VERIFYCERT": "Uncheck this box if your OIDC server is hosted via self-signed certificate.",
|
||||
"OIDC_AUTOONBOARD": "Skip the onboarding screen, so user cannot change its username. Username is provided from ID Token",
|
||||
"OIDC_USER_CLAIM": "The name of the claim in the ID Token where the username is retrieved from. If not specified, it will default to 'name'",
|
||||
"NEW_SECRET": "The secret must longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number"
|
||||
"NEW_SECRET": "The secret must longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number",
|
||||
"OIDC_LOGOUT": "Logs the user out of their current session with the Identity Provider."
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "Enter current password",
|
||||
|
@ -987,7 +988,8 @@
|
|||
"OIDC_ADMIN_GROUP": "OIDC Admin Group",
|
||||
"OIDC_ADMIN_GROUP_INFO": "Specify an OIDC admin group name. All OIDC users in this group will have harbor admin privilege. Keep it blank if you do not want to.",
|
||||
"OIDC_GROUP_FILTER": "OIDC Group Filter",
|
||||
"OIDC_GROUP_FILTER_INFO": "Filter OIDC groups who match the provided regular expression.Keep it blank to match all the groups."
|
||||
"OIDC_GROUP_FILTER_INFO": "Filter OIDC groups who match the provided regular expression.Keep it blank to match all the groups.",
|
||||
"OIDC_LOGOUT": "OIDC Session Logout",
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "Trigger stopping scan all successfully!",
|
||||
|
|
|
@ -106,7 +106,8 @@
|
|||
"OIDC_VERIFYCERT": "Desmarque esta casilla si tu OIDC servidor está alojado a través de certificado autofirmado.",
|
||||
"OIDC_AUTOONBOARD": "Omita la pantalla de incorporación, por lo que el usuario no puede cambiar su nombre de username. El username se proporciona a partir del ID Token",
|
||||
"OIDC_USER_CLAIM": "El nombre del reclamo en el ID Token de donde se obtiene el username. Si no se especifica, se utilizará de forma predeterminada a 'name'.",
|
||||
"NEW_SECRET": "El secreto debe tener más de 8 caracteres con al menos 1 letra mayúscula, 1 letra minúscula y 1 número."
|
||||
"NEW_SECRET": "El secreto debe tener más de 8 caracteres con al menos 1 letra mayúscula, 1 letra minúscula y 1 número.",
|
||||
"OIDC_LOGOUT": "Logs the user out of their current session with the Identity Provider."
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "Introduzca la contraseña actual",
|
||||
|
@ -984,7 +985,8 @@
|
|||
"OIDC_ADMIN_GROUP": "OIDC Grupo Admin",
|
||||
"OIDC_ADMIN_GROUP_INFO": "Especifique un nombre de grupo de administración de OIDC. Todos los usuarios de OIDC en este grupo tendrán privilegios de administrador de puerto. Déjelo en blanco si no lo desea.",
|
||||
"OIDC_GROUP_FILTER": "OIDC Filtro Grupo",
|
||||
"OIDC_GROUP_FILTER_INFO": "Filtrar grupos OIDC que coincidan con la expresión regular proporcionada. Déjelo en blanco para que coincida con todos los grupos."
|
||||
"OIDC_GROUP_FILTER_INFO": "Filtrar grupos OIDC que coincidan con la expresión regular proporcionada. Déjelo en blanco para que coincida con todos los grupos.",
|
||||
"OIDC_LOGOUT": "OIDC Session Logout",
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "Activar detener escanear todos satisfactoriamente!",
|
||||
|
|
|
@ -106,7 +106,8 @@
|
|||
"OIDC_VERIFYCERT": "Décocher cette case si votre serveur OIDC est hébergé avec un certificat auto-signé.",
|
||||
"OIDC_AUTOONBOARD": "Ignorer l'écran d'accueil afin que l'utilisateur ne puisse pas modifier son nom d'utilisateur. Le nom d'utilisateur est fourni à partir du Token ID.",
|
||||
"OIDC_USER_CLAIM": "Le nom du claim dans le ID Token à partir duquel le nom d'utilisateur est récupéré. S'il n'est pas spécifié, la valeur par défaut sera 'name'.",
|
||||
"NEW_SECRET": "Le secret doit être composé de 8 à 128 caractères avec au moins 1 majuscule, 1 minuscule et 1 chiffre."
|
||||
"NEW_SECRET": "Le secret doit être composé de 8 à 128 caractères avec au moins 1 majuscule, 1 minuscule et 1 chiffre.",
|
||||
"OIDC_LOGOUT": "Logs the user out of their current session with the Identity Provider."
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "Entrez le mot de passe actuel",
|
||||
|
@ -987,7 +988,8 @@
|
|||
"OIDC_ADMIN_GROUP": "Groupe d'admin OIDC",
|
||||
"OIDC_ADMIN_GROUP_INFO": "Spécifie le nom d'un groupe d'admin OIDC. Tous les utilisateurs OIDC de ce groupe auront les droits d'admin dans Harbor. Laisser vide pour ne pas l'utiliser.",
|
||||
"OIDC_GROUP_FILTER": "Filtre de groupe OIDC",
|
||||
"OIDC_GROUP_FILTER_INFO": "Filtre les groupes OIDC qui correspondent à l'expression régulière fournie. Laisser vide pour ne pas l'utiliser."
|
||||
"OIDC_GROUP_FILTER_INFO": "Filtre les groupes OIDC qui correspondent à l'expression régulière fournie. Laisser vide pour ne pas l'utiliser.",
|
||||
"OIDC_LOGOUT": "OIDC Session Logout",
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "Déclenchement avec succès de l'arrêt de l'analyse globale !",
|
||||
|
|
|
@ -106,7 +106,8 @@
|
|||
"OIDC_VERIFYCERT": "OIDC 서버가 자체 서명된 인증서를 통해 호스팅되는 경우 이 상자를 선택 취소하세요.",
|
||||
"OIDC_AUTOONBOARD": "온보딩 화면을 건너뛰면 사용자가 사용자 이름을 변경할 수 없습니다. 사용자 이름은 ID 토큰에서 제공됩니다.",
|
||||
"OIDC_USER_CLAIM": "사용자 이름을 검색하는 ID 토큰의 클레임 이름입니다. 지정하지 않으면 기본값은 'name'입니다.",
|
||||
"NEW_SECRET": "시크릿은 대문자 1개, 소문자 1개, 숫자 1개 이상이 포함된 8자 이상이여야합니다"
|
||||
"NEW_SECRET": "시크릿은 대문자 1개, 소문자 1개, 숫자 1개 이상이 포함된 8자 이상이여야합니다",
|
||||
"OIDC_LOGOUT": "Logs the user out of their current session with the Identity Provider."
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "현재 비밀번호를 입력하세요",
|
||||
|
@ -982,7 +983,8 @@
|
|||
"OIDC_ADMIN_GROUP": "OIDC 관리 그룹",
|
||||
"OIDC_ADMIN_GROUP_INFO": "OIDC 관리자 그룹 이름을 지정합니다. 이 그룹의 모든 OIDC 사용자는 항구 관리자 권한을 갖습니다. 원하지 않으시면 비워두세요.",
|
||||
"OIDC_GROUP_FILTER": "OIDC 그룹 필터",
|
||||
"OIDC_GROUP_FILTER_INFO": "제공된 정규식과 일치하는 OIDC 그룹을 필터링합니다. 모든 그룹과 일치하려면 공백으로 유지하세요."
|
||||
"OIDC_GROUP_FILTER_INFO": "제공된 정규식과 일치하는 OIDC 그룹을 필터링합니다. 모든 그룹과 일치하려면 공백으로 유지하세요.",
|
||||
"OIDC_LOGOUT": "OIDC Session Logout",
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "트리거 정지 스캔이 모두 성공적으로 완료되었습니다!",
|
||||
|
|
|
@ -105,7 +105,8 @@
|
|||
"OIDC_VERIFYCERT": "Desmarque para ignorar certificados inválidos ou auto-assinados no provedor ODIC.",
|
||||
"OIDC_AUTOONBOARD": "Pular tela de alteração durante o cadastro automático. Informações, como nome e e-mail, virão do provedor externo.",
|
||||
"OIDC_USER_CLAIM": "Nome da propriedade (claim) cujo valor representa o nome de usuário (login). Se não informado, 'name' será usado.",
|
||||
"NEW_SECRET": "Deve ter mais de 8 caracteres e pelo menos 1 letra maiúscula, 1 minúscula e 1 número."
|
||||
"NEW_SECRET": "Deve ter mais de 8 caracteres e pelo menos 1 letra maiúscula, 1 minúscula e 1 número.",
|
||||
"OIDC_LOGOUT": "Logs the user out of their current session with the Identity Provider."
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "Informe a senha atual",
|
||||
|
@ -982,7 +983,8 @@
|
|||
"OIDC_ADMIN_GROUP": "Grupo Administrativo",
|
||||
"OIDC_ADMIN_GROUP_INFO": "Informe o nome do grupo OIDC que será considerado administrativo. Todos os usuários deste grupo obterão privilégios de adiministração no Harbor. Deixe vazio para ser ignorado.",
|
||||
"OIDC_GROUP_FILTER": "OIDC Group Filter",
|
||||
"OIDC_GROUP_FILTER_INFO": "Filter OIDC groups who match the provided regular expression.Keep it blank to match all the groups."
|
||||
"OIDC_GROUP_FILTER_INFO": "Filter OIDC groups who match the provided regular expression.Keep it blank to match all the groups.",
|
||||
"OIDC_LOGOUT": "OIDC Session Logout",
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "Todos os exames foram interrompidos!",
|
||||
|
|
|
@ -106,7 +106,8 @@
|
|||
"OIDC_VERIFYCERT": "OIDC sunucunuz kendinden imzalı sertifika ile barındırılıyorsa bu kutunun işaretini kaldırın.",
|
||||
"OIDC_AUTOONBOARD": "Skip the onboarding screen, so user cannot change its username. Username is provided from ID Token",
|
||||
"OIDC_USER_CLAIM": "The name of the claim in the ID Token where the username is retrieved from. If not specified, it will default to 'name'",
|
||||
"NEW_SECRET": "The secret must longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number."
|
||||
"NEW_SECRET": "The secret must longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number.",
|
||||
"OIDC_LOGOUT": "Logs the user out of their current session with the Identity Provider."
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "Güncel şifrenizi giriniz",
|
||||
|
@ -986,7 +987,8 @@
|
|||
"OIDC_ADMIN_GROUP": "OIDC Admin Group",
|
||||
"OIDC_ADMIN_GROUP_INFO": "Specify an OIDC admin group name. All OIDC users in this group will have harbor admin privilege. Keep it blank if you do not want to.",
|
||||
"OIDC_GROUP_FILTER": "OIDC Group Filter",
|
||||
"OIDC_GROUP_FILTER_INFO": "Filter OIDC groups who match the provided regular expression.Keep it blank to match all the groups."
|
||||
"OIDC_GROUP_FILTER_INFO": "Filter OIDC groups who match the provided regular expression.Keep it blank to match all the groups.",
|
||||
"OIDC_LOGOUT": "OIDC Session Logout",
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "Trigger stopping scan all successfully!",
|
||||
|
|
|
@ -105,7 +105,8 @@
|
|||
"OIDC_VERIFYCERT": "如果您的OIDC服务器是通过自签名证书托管的,请取消选中此框。",
|
||||
"OIDC_AUTOONBOARD": "跳过登录界面,这样用户就不能更改其用户名。用户名是从ID令牌中获取的",
|
||||
"OIDC_USER_CLAIM": "指定从ID令牌中获取的名称。如果未指定,则默认为'name'",
|
||||
"NEW_SECRET": "Cli secret 必须超过8个字符,并至少包含1个大写字母,1个小写字母和1个数字。"
|
||||
"NEW_SECRET": "Cli secret 必须超过8个字符,并至少包含1个大写字母,1个小写字母和1个数字。",
|
||||
"OIDC_LOGOUT": "Logs the user out of their current session with the Identity Provider.",
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "输入当前密码",
|
||||
|
@ -986,7 +987,8 @@
|
|||
"OIDC_ADMIN_GROUP": "OIDC管理员组",
|
||||
"OIDC_ADMIN_GROUP_INFO": "OIDC管理员组名称。所有该组内用户都会有管理员权限,此属性可以为空。",
|
||||
"OIDC_GROUP_FILTER": "OIDC 组过滤器",
|
||||
"OIDC_GROUP_FILTER_INFO": "该过滤器将会过滤掉不匹配此项正则表达式的 OIDC 组。若不填,则表示匹配所有。"
|
||||
"OIDC_GROUP_FILTER_INFO": "该过滤器将会过滤掉不匹配此项正则表达式的 OIDC 组。若不填,则表示匹配所有。",
|
||||
"OIDC_LOGOUT": "OIDC Session Logout"
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "停止扫描所有镜像任务成功!",
|
||||
|
|
|
@ -105,7 +105,8 @@
|
|||
"OIDC_VERIFYCERT": "如果您的 OIDC 伺服器是透過自簽憑證託管的,請取消勾選此框。",
|
||||
"OIDC_AUTOONBOARD": "跳過註冊引導畫面,使用者無法更改其使用者名稱。使用者名稱將由 ID Token 提供。",
|
||||
"OIDC_USER_CLAIM": "使用者名稱將從 ID Token 中的特定宣告中取得。若未指定特定宣告,則預設會使用 'name' 宣告。",
|
||||
"NEW_SECRET": "密碼必須超過 8 個字元,並至少包含 1 個大寫字母、1 個小寫字母和 1 個數字。"
|
||||
"NEW_SECRET": "密碼必須超過 8 個字元,並至少包含 1 個大寫字母、1 個小寫字母和 1 個數字。",
|
||||
"OIDC_LOGOUT": "Logs the user out of their current session with the Identity Provider."
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "輸入目前密碼",
|
||||
|
@ -983,7 +984,8 @@
|
|||
"OIDC_ADMIN_GROUP": "OIDC 管理員群組",
|
||||
"OIDC_ADMIN_GROUP_INFO": "指定 OIDC 管理員群組名稱。此群組中的所有 OIDC 使用者都將具有 Harbor 管理員權限。如果您不需要,此欄可以留空。",
|
||||
"OIDC_GROUP_FILTER": "OIDC 群組篩選器",
|
||||
"OIDC_GROUP_FILTER_INFO": "篩選符合提供的正規表達式的 OIDC 群組。保持空白以選取所有群組。"
|
||||
"OIDC_GROUP_FILTER_INFO": "篩選符合提供的正規表達式的 OIDC 群組。保持空白以選取所有群組。",
|
||||
"OIDC_LOGOUT": "OIDC Session Logout",
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "成功觸發停止全部掃描!",
|
||||
|
|
|
@ -40,6 +40,7 @@ func registerRoutes() {
|
|||
web.Router("/c/log_out", &controllers.CommonController{}, "get:LogOut")
|
||||
web.Router("/c/userExists", &controllers.CommonController{}, "post:UserExists")
|
||||
web.Router(common.OIDCLoginPath, &controllers.OIDCController{}, "get:RedirectLogin")
|
||||
web.Router(common.OIDCLoginoutPath, &controllers.OIDCController{}, "get:RedirectLogout")
|
||||
web.Router("/c/oidc/onboard", &controllers.OIDCController{}, "post:Onboard")
|
||||
web.Router(common.OIDCCallbackPath, &controllers.OIDCController{}, "get:Callback")
|
||||
web.Router(common.AuthProxyRedirectPath, &controllers.AuthProxyController{}, "get:HandleRedirect")
|
||||
|
|
Loading…
Reference in New Issue
Block a user