mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 10:15:35 +01:00
Refine request handle process (#9760)
* Refine request handle process Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
parent
b87373d6a9
commit
06e4e124d8
@ -4,3 +4,6 @@ enablegzip = true
|
||||
|
||||
[dev]
|
||||
httpport = 8080
|
||||
EnableXSRF = true
|
||||
XSRFKey = {{xsrf_key}}
|
||||
XSRFExpire = 3600
|
||||
|
@ -35,8 +35,13 @@ def prepare_core(config_dict, with_notary, with_clair, with_chartmuseum):
|
||||
with_chartmuseum=with_chartmuseum,
|
||||
**config_dict)
|
||||
|
||||
# Copy Core app.conf
|
||||
copy_core_config(core_conf_template_path, core_conf)
|
||||
render_jinja(
|
||||
core_conf_template_path,
|
||||
core_conf,
|
||||
uid=DEFAULT_UID,
|
||||
gid=DEFAULT_GID,
|
||||
xsrf_key=generate_random_string(40))
|
||||
|
||||
|
||||
|
||||
def copy_core_config(core_templates_path, core_config_path):
|
||||
|
@ -83,6 +83,10 @@ func (b *BaseController) Prepare() {
|
||||
return
|
||||
}
|
||||
b.ProjectMgr = pm
|
||||
|
||||
if !filter.ReqCarriesSession(b.Ctx.Request) {
|
||||
b.EnableXSRF = false
|
||||
}
|
||||
}
|
||||
|
||||
// RequireAuthenticated returns true when the request is authenticated
|
||||
|
@ -92,6 +92,7 @@ func init() {
|
||||
beego.TestBeegoInit(apppath)
|
||||
|
||||
filter.Init()
|
||||
beego.InsertFilter("/api/*", beego.BeforeStatic, filter.SessionCheck)
|
||||
beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter)
|
||||
|
||||
beego.Router("/api/health", &HealthAPI{}, "get:CheckHealth")
|
||||
|
@ -17,6 +17,7 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/core/filter"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@ -317,6 +318,11 @@ func (ua *UserAPI) Post() {
|
||||
return
|
||||
}
|
||||
|
||||
if !ua.IsAdmin && !filter.ReqCarriesSession(ua.Ctx.Request) {
|
||||
ua.SendForbiddenError(errors.New("self-registration cannot be triggered via API"))
|
||||
return
|
||||
}
|
||||
|
||||
user := models.User{}
|
||||
if err := ua.DecodeJSONReq(&user); err != nil {
|
||||
ua.SendBadRequestError(err)
|
||||
|
@ -51,7 +51,8 @@ func TestUsersPost(t *testing.T) {
|
||||
t.Error("Error occurred while add a test User", err.Error())
|
||||
t.Log(err)
|
||||
} else {
|
||||
assert.Equal(400, code, "case 1: Add user status should be 400")
|
||||
// Should be 403 as only admin can call this API, otherwise it has to be called from browser, with session id
|
||||
assert.Equal(http.StatusForbidden, code, "case 1: Add user status should be 400")
|
||||
}
|
||||
|
||||
// case 2: register a new user with admin auth, but username is empty, expect 400
|
||||
|
@ -42,6 +42,9 @@ const (
|
||||
defaultKeyPath = "/etc/core/key"
|
||||
defaultTokenFilePath = "/etc/core/token/tokens.properties"
|
||||
defaultRegistryTokenPrivateKeyPath = "/etc/core/private_key.pem"
|
||||
|
||||
// SessionCookieName is the name of the cookie for session ID
|
||||
SessionCookieName = "sid"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -106,10 +106,13 @@ func TestAll(t *testing.T) {
|
||||
err := middlewares.Init()
|
||||
assert.Nil(err)
|
||||
|
||||
// Has to set to dev so that the xsrf panic can be rendered as 403
|
||||
beego.BConfig.RunMode = beego.DEV
|
||||
|
||||
r, _ := http.NewRequest("POST", "/c/login", nil)
|
||||
w := httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(401), w.Code, "'/c/login' httpStatusCode should be 401")
|
||||
assert.Equal(http.StatusForbidden, w.Code, "'/c/login' httpStatusCode should be 403")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/c/log_out", nil)
|
||||
w = httptest.NewRecorder()
|
||||
@ -120,12 +123,12 @@ func TestAll(t *testing.T) {
|
||||
r, _ = http.NewRequest("POST", "/c/reset", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(400), w.Code, "'/c/reset' httpStatusCode should be 400")
|
||||
assert.Equal(http.StatusForbidden, w.Code, "'/c/reset' httpStatusCode should be 403")
|
||||
|
||||
r, _ = http.NewRequest("POST", "/c/userExists", nil)
|
||||
w = httptest.NewRecorder()
|
||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||
assert.Equal(int(500), w.Code, "'/c/userExists' httpStatusCode should be 500")
|
||||
assert.Equal(http.StatusForbidden, w.Code, "'/c/userExists' httpStatusCode should be 403")
|
||||
|
||||
r, _ = http.NewRequest("GET", "/c/sendEmail", nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
@ -10,6 +10,11 @@ type RegistryProxy struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// Prepare turn off the xsrf check for registry proxy
|
||||
func (p *RegistryProxy) Prepare() {
|
||||
p.EnableXSRF = false
|
||||
}
|
||||
|
||||
// Handle is the only entrypoint for incoming requests, all requests must go through this func.
|
||||
func (p *RegistryProxy) Handle() {
|
||||
req := p.Ctx.Request
|
||||
|
30
src/core/filter/sessionchecker.go
Normal file
30
src/core/filter/sessionchecker.go
Normal file
@ -0,0 +1,30 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"context"
|
||||
beegoctx "github.com/astaxie/beego/context"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// SessionReqKey is the key in the context of a request to mark the request carries session when reaching the backend
|
||||
const SessionReqKey ContextValueKey = "harbor_with_session_req"
|
||||
|
||||
// SessionCheck is a filter to mark the requests that carries a session id, it has to be registered as
|
||||
// "beego.BeforeStatic" because beego will modify the request after execution of these filters, all requests will
|
||||
// appear to have a session id cookie.
|
||||
func SessionCheck(ctx *beegoctx.Context) {
|
||||
req := ctx.Request
|
||||
_, err := req.Cookie(config.SessionCookieName)
|
||||
if err == nil {
|
||||
ctx.Request = req.WithContext(context.WithValue(req.Context(), SessionReqKey, true))
|
||||
log.Debug("Mark the request as no-session")
|
||||
}
|
||||
}
|
||||
|
||||
// ReqCarriesSession verifies if the request carries session when
|
||||
func ReqCarriesSession(req *http.Request) bool {
|
||||
r, ok := req.Context().Value(SessionReqKey).(bool)
|
||||
return ok && r
|
||||
}
|
16
src/core/filter/sessionchecker_test.go
Normal file
16
src/core/filter/sessionchecker_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
beegoctx "github.com/astaxie/beego/context"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReqHasNoSession(t *testing.T) {
|
||||
req, _ := http.NewRequest("POST", "https://127.0.0.1:8080/api/users", nil)
|
||||
ctx := beegoctx.NewContext()
|
||||
ctx.Request = req
|
||||
SessionCheck(ctx)
|
||||
assert.False(t, ReqCarriesSession(ctx.Request))
|
||||
}
|
@ -164,7 +164,7 @@ func gracefulShutdown(closing, done chan struct{}) {
|
||||
|
||||
func main() {
|
||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||
beego.BConfig.WebConfig.Session.SessionName = "sid"
|
||||
beego.BConfig.WebConfig.Session.SessionName = config.SessionCookieName
|
||||
|
||||
redisURL := os.Getenv("_REDIS_URL")
|
||||
if len(redisURL) > 0 {
|
||||
@ -244,9 +244,9 @@ func main() {
|
||||
event.Init()
|
||||
|
||||
filter.Init()
|
||||
beego.InsertFilter("/api/*", beego.BeforeStatic, filter.SessionCheck)
|
||||
beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter)
|
||||
beego.InsertFilter("/*", beego.BeforeRouter, filter.ReadonlyFilter)
|
||||
beego.InsertFilter("/api/*", beego.BeforeRouter, filter.MediaTypeFilter("application/json", "multipart/form-data", "application/octet-stream"))
|
||||
|
||||
initRouters()
|
||||
|
||||
|
@ -16,13 +16,13 @@ package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/goharbor/harbor/src/core/service/notifications"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/job"
|
||||
job_model "github.com/goharbor/harbor/src/common/job/models"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/api"
|
||||
j "github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/api/scan"
|
||||
)
|
||||
@ -39,7 +39,7 @@ var statusMap = map[string]string{
|
||||
|
||||
// Handler handles request on /service/notifications/jobs/adminjob/*, which listens to the webhook of jobservice.
|
||||
type Handler struct {
|
||||
api.BaseController
|
||||
notifications.BaseHandler
|
||||
id int64
|
||||
UUID string
|
||||
status string
|
||||
@ -52,6 +52,7 @@ type Handler struct {
|
||||
|
||||
// Prepare ...
|
||||
func (h *Handler) Prepare() {
|
||||
h.BaseHandler.Prepare()
|
||||
var data job_model.JobStatusChange
|
||||
err := json.Unmarshal(h.Ctx.Input.CopyBody(1<<32), &data)
|
||||
if err != nil {
|
||||
|
13
src/core/service/notifications/base.go
Normal file
13
src/core/service/notifications/base.go
Normal file
@ -0,0 +1,13 @@
|
||||
package notifications
|
||||
|
||||
import "github.com/goharbor/harbor/src/core/api"
|
||||
|
||||
// BaseHandler extracts the common funcs, all notification handlers should shadow this struct
|
||||
type BaseHandler struct {
|
||||
api.BaseController
|
||||
}
|
||||
|
||||
// Prepare disable the xsrf as the request is from other components and do not require the xsrf token
|
||||
func (bh *BaseHandler) Prepare() {
|
||||
bh.EnableXSRF = false
|
||||
}
|
@ -16,12 +16,12 @@ package jobs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/goharbor/harbor/src/core/service/notifications"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/job"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/api"
|
||||
"github.com/goharbor/harbor/src/core/notifier/event"
|
||||
jjob "github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
@ -45,7 +45,7 @@ var statusMap = map[string]string{
|
||||
|
||||
// Handler handles request on /service/notifications/jobs/*, which listens to the webhook of jobservice.
|
||||
type Handler struct {
|
||||
api.BaseController
|
||||
notifications.BaseHandler
|
||||
id int64
|
||||
status string
|
||||
rawStatus string
|
||||
@ -57,6 +57,7 @@ type Handler struct {
|
||||
|
||||
// Prepare ...
|
||||
func (h *Handler) Prepare() {
|
||||
h.BaseHandler.Prepare()
|
||||
h.trackID = h.GetStringFromPath(":uuid")
|
||||
if len(h.trackID) == 0 {
|
||||
id, err := h.GetInt64FromPath(":id")
|
||||
|
@ -16,6 +16,7 @@ package registry
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/goharbor/harbor/src/core/service/notifications"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -25,7 +26,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/api"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
notifierEvt "github.com/goharbor/harbor/src/core/notifier/event"
|
||||
coreutils "github.com/goharbor/harbor/src/core/utils"
|
||||
@ -41,7 +41,7 @@ import (
|
||||
|
||||
// NotificationHandler handles request on /service/notifications/, which listens to registry's events.
|
||||
type NotificationHandler struct {
|
||||
api.BaseController
|
||||
notifications.BaseHandler
|
||||
}
|
||||
|
||||
const manifestPattern = `^application/vnd.docker.distribution.manifest.v\d\+(json|prettyjws)`
|
||||
|
@ -17,17 +17,17 @@ package scheduler
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/core/service/notifications"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/job/models"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/api"
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler"
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler/hook"
|
||||
)
|
||||
|
||||
// Handler handles the scheduler requests
|
||||
type Handler struct {
|
||||
api.BaseController
|
||||
notifications.BaseHandler
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
|
@ -0,0 +1,49 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { HttpXsrfTokenExtractorToBeUsed } from './http-xsrf-token-extractor.service';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { CookieService } from "ngx-cookie";
|
||||
|
||||
describe('HttpXsrfTokenExtractorToBeUsed', () => {
|
||||
let cookie = "fdsa|ds";
|
||||
let mockCookieService = {
|
||||
get: function () {
|
||||
return cookie;
|
||||
},
|
||||
set: function (cookieStr: string) {
|
||||
cookie = cookieStr;
|
||||
}
|
||||
};
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedModule
|
||||
],
|
||||
providers: [
|
||||
HttpXsrfTokenExtractorToBeUsed,
|
||||
{ provide: CookieService, useValue: mockCookieService}
|
||||
]
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should be initialized', inject([HttpXsrfTokenExtractorToBeUsed], (service: HttpXsrfTokenExtractorToBeUsed) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should be get right token when the cookie exists', inject([HttpXsrfTokenExtractorToBeUsed],
|
||||
(service: HttpXsrfTokenExtractorToBeUsed) => {
|
||||
mockCookieService.set("fdsa|ds");
|
||||
let token = service.getToken();
|
||||
expect(btoa(token)).toEqual(cookie.split("|")[0]);
|
||||
}));
|
||||
|
||||
it('should be get right token when the cookie does not exist', inject([HttpXsrfTokenExtractorToBeUsed],
|
||||
(service: HttpXsrfTokenExtractorToBeUsed) => {
|
||||
mockCookieService.set(null);
|
||||
let token = service.getToken();
|
||||
expect(token).toBeNull();
|
||||
}));
|
||||
|
||||
|
||||
});
|
@ -0,0 +1,18 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { HttpXsrfTokenExtractor } from "@angular/common/http";
|
||||
import { CookieService } from "ngx-cookie";
|
||||
@Injectable()
|
||||
export class HttpXsrfTokenExtractorToBeUsed extends HttpXsrfTokenExtractor {
|
||||
constructor(
|
||||
private cookieService: CookieService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
public getToken(): string | null {
|
||||
const csrfCookie = this.cookieService.get("_xsrf");
|
||||
if (csrfCookie) {
|
||||
return atob(csrfCookie.split("|")[0]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { HttpClientModule, HttpClient} from '@angular/common/http';
|
||||
import { HttpClientModule, HttpClientXsrfModule, HttpClient, HttpXsrfTokenExtractor } from '@angular/common/http';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { TranslateModule, TranslateLoader, MissingTranslationHandler } from '@ngx-translate/core';
|
||||
@ -12,6 +12,7 @@ import { ClipboardModule } from '../third-party/ngx-clipboard/index';
|
||||
import { MyMissingTranslationHandler } from '../i18n/missing-trans.handler';
|
||||
import { TranslatorJsonLoader } from '../i18n/local-json.loader';
|
||||
import { IServiceConfig, SERVICE_CONFIG } from '../service.config';
|
||||
import { HttpXsrfTokenExtractorToBeUsed } from '../service/http-xsrf-token-extractor.service';
|
||||
|
||||
/*export function HttpLoaderFactory(http: Http) {
|
||||
return new TranslateHttpLoader(http, 'i18n/lang/', '-lang.json');
|
||||
@ -42,6 +43,10 @@ export function GeneralTranslatorLoader(http: HttpClient, config: IServiceConfig
|
||||
imports: [
|
||||
CommonModule,
|
||||
HttpClientModule,
|
||||
HttpClientXsrfModule.withOptions({
|
||||
cookieName: '_xsrf',
|
||||
headerName: 'X-Xsrftoken'
|
||||
}),
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
ClipboardModule,
|
||||
@ -71,6 +76,8 @@ export function GeneralTranslatorLoader(http: HttpClient, config: IServiceConfig
|
||||
MarkdownModule,
|
||||
TranslateModule,
|
||||
],
|
||||
providers: [CookieService]
|
||||
providers: [
|
||||
CookieService,
|
||||
{ provide: HttpXsrfTokenExtractor, useClass: HttpXsrfTokenExtractorToBeUsed }]
|
||||
})
|
||||
export class SharedModule { }
|
||||
|
@ -2,11 +2,16 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { DevCenterComponent } from './dev-center.component';
|
||||
import { CookieService } from 'ngx-cookie';
|
||||
|
||||
describe('DevCenterComponent', () => {
|
||||
let component: DevCenterComponent;
|
||||
let fixture: ComponentFixture<DevCenterComponent>;
|
||||
|
||||
const mockCookieService = {
|
||||
get: () => {
|
||||
return "xsrf";
|
||||
}
|
||||
};
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DevCenterComponent],
|
||||
@ -15,7 +20,10 @@ describe('DevCenterComponent', () => {
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
TranslateService
|
||||
TranslateService,
|
||||
{
|
||||
provide: CookieService, useValue: mockCookieService
|
||||
}
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
@ -4,7 +4,7 @@ import { throwError as observableThrowError, Observable } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { CookieService } from "ngx-cookie";
|
||||
|
||||
const SwaggerUI = require('swagger-ui');
|
||||
@Component({
|
||||
@ -21,6 +21,7 @@ export class DevCenterComponent implements AfterViewInit, OnInit {
|
||||
private el: ElementRef,
|
||||
private http: HttpClient,
|
||||
private translate: TranslateService,
|
||||
private cookieService: CookieService,
|
||||
private titleService: Title) {
|
||||
}
|
||||
|
||||
@ -28,28 +29,47 @@ export class DevCenterComponent implements AfterViewInit, OnInit {
|
||||
this.setTitle("APP_TITLE.HARBOR_SWAGGER");
|
||||
}
|
||||
|
||||
|
||||
public setTitle( key: string) {
|
||||
public setTitle(key: string) {
|
||||
this.translate.get(key).subscribe((res: string) => {
|
||||
this.titleService.setTitle(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
const csrfCookie = this.cookieService.get('_xsrf');
|
||||
const interceptor = {
|
||||
requestInterceptor: {
|
||||
apply: function (requestObj) {
|
||||
const headers = requestObj.headers || {};
|
||||
if (csrfCookie) {
|
||||
headers["X-Xsrftoken"] = atob(csrfCookie.split("|")[0]);
|
||||
}
|
||||
return requestObj;
|
||||
}
|
||||
}
|
||||
};
|
||||
this.http.get("/swagger.json")
|
||||
.pipe(catchError(error => observableThrowError(error)))
|
||||
.subscribe(json => {
|
||||
json['host'] = window.location.host;
|
||||
const protocal = window.location.protocol;
|
||||
json['schemes'] = [protocal.replace(":", "")];
|
||||
let ui = SwaggerUI({
|
||||
spec: json,
|
||||
domNode: this.el.nativeElement.querySelector('.swagger-container'),
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUI.presets.apis
|
||||
],
|
||||
.pipe(catchError(error => observableThrowError(error)))
|
||||
.subscribe(json => {
|
||||
json['host'] = window.location.host;
|
||||
const protocal = window.location.protocol;
|
||||
json['schemes'] = [protocal.replace(":", "")];
|
||||
let ui = SwaggerUI({
|
||||
spec: json,
|
||||
domNode: this.el.nativeElement.querySelector('.swagger-container'),
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUI.presets.apis
|
||||
],
|
||||
requestInterceptor: interceptor.requestInterceptor,
|
||||
authorizations: {
|
||||
csrf: function () {
|
||||
this.headers['X-Xsrftoken'] = csrfCookie;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user