mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-19 21:32:24 +01:00
Merge pull request #12826 from goharbor/csrf-local
CSRF token cookie -> header
This commit is contained in:
commit
29f3ced3ff
@ -1,20 +1,10 @@
|
|||||||
import { TestBed, inject } from '@angular/core/testing';
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
import { InterceptHttpService } from './intercept-http.service';
|
import { InterceptHttpService } from './intercept-http.service';
|
||||||
import { CookieService } from 'ngx-cookie';
|
|
||||||
import { HttpRequest, HttpResponse } from '@angular/common/http';
|
import { HttpRequest, HttpResponse } from '@angular/common/http';
|
||||||
import { of, throwError } from 'rxjs';
|
import { of, throwError } from 'rxjs';
|
||||||
|
|
||||||
describe('InterceptHttpService', () => {
|
describe('InterceptHttpService', () => {
|
||||||
let cookie = "fdsa|ds";
|
const mockedCSRFToken: string = 'test';
|
||||||
const mockCookieService = {
|
|
||||||
get: function () {
|
|
||||||
return cookie;
|
|
||||||
},
|
|
||||||
set: function (cookieStr: string) {
|
|
||||||
cookie = cookieStr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const mockRequest = new HttpRequest('PUT', "", {
|
const mockRequest = new HttpRequest('PUT', "", {
|
||||||
headers: new Map()
|
headers: new Map()
|
||||||
});
|
});
|
||||||
@ -29,13 +19,21 @@ describe('InterceptHttpService', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
beforeEach(() => TestBed.configureTestingModule({}));
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
let store = {};
|
||||||
|
spyOn(localStorage, 'getItem').and.callFake( key => {
|
||||||
|
return store[key];
|
||||||
|
});
|
||||||
|
spyOn(localStorage, 'setItem').and.callFake((key, value) => {
|
||||||
|
return store[key] = value + '';
|
||||||
|
});
|
||||||
|
spyOn(localStorage, 'clear').and.callFake( () => {
|
||||||
|
store = {};
|
||||||
|
});
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [],
|
imports: [],
|
||||||
providers: [
|
providers: [
|
||||||
InterceptHttpService,
|
InterceptHttpService
|
||||||
{ provide: CookieService, useValue: mockCookieService }
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -46,10 +44,10 @@ describe('InterceptHttpService', () => {
|
|||||||
|
|
||||||
it('should be get right token and send right request when the cookie not exists', inject([InterceptHttpService],
|
it('should be get right token and send right request when the cookie not exists', inject([InterceptHttpService],
|
||||||
(service: InterceptHttpService) => {
|
(service: InterceptHttpService) => {
|
||||||
mockCookieService.set("fdsa|ds");
|
localStorage.setItem("__csrf", mockedCSRFToken);
|
||||||
service.intercept(mockRequest, mockHandle).subscribe(res => {
|
service.intercept(mockRequest, mockHandle).subscribe(res => {
|
||||||
if (res.status === 403) {
|
if (res.status === 403) {
|
||||||
expect(mockRequest.headers.get("X-Harbor-CSRF-Token")).toEqual(cookie);
|
expect(mockRequest.headers.get("X-Harbor-CSRF-Token")).toEqual(mockedCSRFToken);
|
||||||
} else {
|
} else {
|
||||||
expect(res.status).toEqual(200);
|
expect(res.status).toEqual(200);
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,58 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse } from '@angular/common/http';
|
import { HttpInterceptor, HttpRequest, HttpHandler, HttpResponse } from '@angular/common/http';
|
||||||
import { Observable, throwError } from 'rxjs';
|
import { Observable, throwError } from 'rxjs';
|
||||||
import { tap, catchError } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
import { CookieService } from 'ngx-cookie';
|
|
||||||
|
const SAFE_METHODS: string[] = ["GET", "HEAD", "OPTIONS", "TRACE"];
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class InterceptHttpService implements HttpInterceptor {
|
export class InterceptHttpService implements HttpInterceptor {
|
||||||
|
|
||||||
constructor(private cookie: CookieService) { }
|
constructor() { }
|
||||||
|
|
||||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
|
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
|
||||||
|
// Get the csrf token from localstorage
|
||||||
return next.handle(request).pipe(catchError(error => {
|
const token = localStorage.getItem("__csrf");
|
||||||
if (error.status === 403) {
|
if (token) {
|
||||||
let Xsrftoken = this.cookie.get("__csrf");
|
// Clone the request and replace the original headers with
|
||||||
if (Xsrftoken && !request.headers.has('X-Harbor-CSRF-Token')) {
|
// cloned headers, updated with the csrf token.
|
||||||
request = request.clone({ headers: request.headers.set('X-Harbor-CSRF-Token', Xsrftoken) });
|
// not for requests using safe methods
|
||||||
return next.handle(request);
|
if (request.method && SAFE_METHODS.indexOf(request.method.toUpperCase()) === -1) {
|
||||||
}
|
request = request.clone({
|
||||||
|
headers: request.headers.set('X-Harbor-CSRF-Token', token)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return throwError(error);
|
}
|
||||||
}));
|
return next.handle(request).pipe(
|
||||||
|
tap(response => {
|
||||||
|
if (response && response instanceof HttpResponse && response.headers) {
|
||||||
|
const responseToken: string = response.headers.get('X-Harbor-CSRF-Token');
|
||||||
|
if (responseToken) {
|
||||||
|
localStorage.setItem("__csrf", responseToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
if (error && error.headers) {
|
||||||
|
const responseToken: string = error.headers.get('X-Harbor-CSRF-Token');
|
||||||
|
if (responseToken) {
|
||||||
|
localStorage.setItem("__csrf", responseToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.pipe(
|
||||||
|
catchError(error => {
|
||||||
|
if (error.status === 403) {
|
||||||
|
const csrfToken = localStorage.getItem("__csrf");
|
||||||
|
if (csrfToken) {
|
||||||
|
request = request.clone({ headers: request.headers.set('X-Harbor-CSRF-Token', csrfToken)});
|
||||||
|
return next.handle(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return throwError(error);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,10 +36,6 @@ export function GeneralTranslatorLoader(http: HttpClient, config: IServiceConfig
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
HttpClientXsrfModule.withOptions({
|
|
||||||
cookieName: '__csrf',
|
|
||||||
headerName: 'X-Harbor-CSRF-Token'
|
|
||||||
}),
|
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
ClipboardModule,
|
ClipboardModule,
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
const (
|
const (
|
||||||
csrfKeyEnv = "CSRF_KEY"
|
csrfKeyEnv = "CSRF_KEY"
|
||||||
tokenHeader = "X-Harbor-CSRF-Token"
|
tokenHeader = "X-Harbor-CSRF-Token"
|
||||||
tokenCookie = "__csrf"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -31,13 +30,7 @@ var (
|
|||||||
// attachToken makes sure if csrf generate a new token it will be included in the response header
|
// attachToken makes sure if csrf generate a new token it will be included in the response header
|
||||||
func attachToken(w http.ResponseWriter, r *http.Request) {
|
func attachToken(w http.ResponseWriter, r *http.Request) {
|
||||||
if t := csrf.Token(r); len(t) > 0 {
|
if t := csrf.Token(r); len(t) > 0 {
|
||||||
http.SetCookie(w, &http.Cookie{
|
w.Header().Set(tokenHeader, t)
|
||||||
Name: tokenCookie,
|
|
||||||
Secure: secureFlag,
|
|
||||||
Value: t,
|
|
||||||
Path: "/",
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
log.Warningf("token not found in context, skip attaching")
|
log.Warningf("token not found in context, skip attaching")
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ func TestMiddleware(t *testing.T) {
|
|||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
srv.ServeHTTP(rec, c.req)
|
srv.ServeHTTP(rec, c.req)
|
||||||
assert.Equal(t, c.statusCode, rec.Result().StatusCode)
|
assert.Equal(t, c.statusCode, rec.Result().StatusCode)
|
||||||
assert.Equal(t, c.returnToken, hasCookie(rec.Result(), tokenCookie))
|
assert.Equal(t, c.returnToken, rec.Result().Header.Get(tokenHeader) != "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user