From 795d33a45a0dcd45408d468c7e1e69831ca56d5a Mon Sep 17 00:00:00 2001 From: Daniel Jiang Date: Fri, 3 Nov 2017 14:43:27 +0800 Subject: [PATCH] Add filter on API endpoints to mitigate XSRF (#3542) Add filter for all API endpoints to allow the POST requests which have application/json header. Make update to UI code to make sure all requests contain the header. --- src/ui/filter/mediatype.go | 43 +++++++++++++++++ src/ui/filter/mediatype_test.go | 42 +++++++++++++++++ src/ui/main.go | 1 + .../project-policy-config.component.ts | 6 +-- .../lib/src/service/access-log.service.ts | 4 +- .../lib/src/service/configuration.service.ts | 6 +-- src/ui_ng/lib/src/service/endpoint.service.ts | 14 +++--- src/ui_ng/lib/src/service/job-log.service.ts | 4 +- src/ui_ng/lib/src/service/project.service.ts | 8 ++-- .../lib/src/service/replication.service.ts | 6 +-- src/ui_ng/lib/src/service/scanning.service.ts | 4 +- .../lib/src/service/system-info.service.ts | 3 +- src/ui_ng/lib/src/service/tag.service.ts | 6 +-- src/ui_ng/lib/src/utils.ts | 13 +++++- src/ui_ng/package.json | 2 +- .../password/password-setting.service.ts | 20 ++------ .../app/account/sign-in/sign-in.service.ts | 6 +-- src/ui_ng/src/app/app-config.service.ts | 12 ++--- .../global-search/global-search.service.ts | 9 +--- src/ui_ng/src/app/config/config.service.ts | 16 ++----- src/ui_ng/src/app/log/audit-log.service.ts | 30 ++++++------ .../src/app/project/member/member.service.ts | 7 +-- src/ui_ng/src/app/project/project.service.ts | 18 ++++---- .../top-repo/top-repository.service.ts | 9 +--- src/ui_ng/src/app/shared/session.service.ts | 23 ++++------ src/ui_ng/src/app/shared/shared.utils.ts | 46 +++++++++++++++++++ .../shared/statictics/statistics.service.ts | 11 ++--- src/ui_ng/src/app/user/user.service.ts | 16 +++---- 28 files changed, 237 insertions(+), 148 deletions(-) create mode 100644 src/ui/filter/mediatype.go create mode 100644 src/ui/filter/mediatype_test.go diff --git a/src/ui/filter/mediatype.go b/src/ui/filter/mediatype.go new file mode 100644 index 000000000..8d2765faf --- /dev/null +++ b/src/ui/filter/mediatype.go @@ -0,0 +1,43 @@ +// Copyright (c) 2017 VMware, Inc. All Rights Reserved. +// +// 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. + +package filter + +import ( + beegoctx "github.com/astaxie/beego/context" + "net/http" + "strings" +) + +//MediaTypeFilter filters the POST request, it returns 415 if the content type of the request +//doesn't match the preset ones. +func MediaTypeFilter(mediaType ...string) func(*beegoctx.Context) { + return func(ctx *beegoctx.Context) { + filterContentType(ctx.Request, ctx.ResponseWriter, mediaType...) + } +} + +func filterContentType(req *http.Request, resp http.ResponseWriter, mediaType ...string) { + if req.Method != http.MethodPost { + return + } + v := req.Header.Get("Content-Type") + mimeType := strings.Split(v, ";")[0] + for _, t := range mediaType { + if t == mimeType { + return + } + } + resp.WriteHeader(http.StatusUnsupportedMediaType) +} diff --git a/src/ui/filter/mediatype_test.go b/src/ui/filter/mediatype_test.go new file mode 100644 index 000000000..d0d9e84dc --- /dev/null +++ b/src/ui/filter/mediatype_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2017 VMware, Inc. All Rights Reserved. +// +// 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. + +package filter + +import ( + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +func TestMediaTypeFilter(t *testing.T) { + assert := assert.New(t) + getReq := httptest.NewRequest(http.MethodGet, "/the/path", nil) + rec := httptest.NewRecorder() + filterContentType(getReq, rec, "application/json") + assert.Equal(http.StatusOK, rec.Code) + + postReq := httptest.NewRequest(http.MethodPost, "/the/path", nil) + postReq.Header.Set("Content-Type", "text/html") + rec2 := httptest.NewRecorder() + filterContentType(postReq, rec2, "application/json") + assert.Equal(http.StatusUnsupportedMediaType, rec2.Code) + postReq2 := httptest.NewRequest(http.MethodPost, "/the/path", nil) + postReq2.Header.Set("Content-Type", "application/json; charset=utf-8") + rec3 := httptest.NewRecorder() + filterContentType(postReq2, rec3, "application/json") + assert.Equal(http.StatusOK, rec3.Code) + +} diff --git a/src/ui/main.go b/src/ui/main.go index a9f7fb859..42d967315 100644 --- a/src/ui/main.go +++ b/src/ui/main.go @@ -132,6 +132,7 @@ func main() { filter.Init() beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter) + beego.InsertFilter("/api/*", beego.BeforeRouter, filter.MediaTypeFilter("application/json")) initRouters() if err := api.SyncRegistry(config.GlobalProjectMgr); err != nil { diff --git a/src/ui_ng/lib/src/project-policy-config/project-policy-config.component.ts b/src/ui_ng/lib/src/project-policy-config/project-policy-config.component.ts index 037ea4da1..dbf623534 100644 --- a/src/ui_ng/lib/src/project-policy-config/project-policy-config.component.ts +++ b/src/ui_ng/lib/src/project-policy-config/project-policy-config.component.ts @@ -85,9 +85,8 @@ export class ProjectPolicyConfigComponent implements OnInit { response => { this.orgProjectPolicy.initByProject(response); this.projectPolicy.initByProject(response); - }, - error => this.errorHandler.error(error) - ); + }) + .catch(error => this.errorHandler.error(error)); } updateProjectPolicy(projectId: string|number, pp: ProjectPolicy) { @@ -125,7 +124,6 @@ export class ProjectPolicyConfigComponent implements OnInit { this.refresh(); }) .catch(error => { - console.log(error); this.onGoing = false; this.errorHandler.error(error); }); diff --git a/src/ui_ng/lib/src/service/access-log.service.ts b/src/ui_ng/lib/src/service/access-log.service.ts index 08e85870c..bfd15c6b5 100644 --- a/src/ui_ng/lib/src/service/access-log.service.ts +++ b/src/ui_ng/lib/src/service/access-log.service.ts @@ -5,7 +5,7 @@ import { Injectable, Inject } from "@angular/core"; import 'rxjs/add/observable/of'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { Http, URLSearchParams } from '@angular/http'; -import { HTTP_JSON_OPTIONS, buildHttpRequestOptions } from '../utils'; +import { buildHttpRequestOptions, HTTP_GET_OPTIONS } from '../utils'; /** * Define service methods to handle the access log related things. @@ -67,7 +67,7 @@ export class AccessLogDefaultService extends AccessLogService { url = '/api/logs'; } - return this.http.get(url, queryParams ? buildHttpRequestOptions(queryParams) : HTTP_JSON_OPTIONS).toPromise() + return this.http.get(url, queryParams ? buildHttpRequestOptions(queryParams) : HTTP_GET_OPTIONS).toPromise() .then(response => { let result: AccessLog = { metadata: { diff --git a/src/ui_ng/lib/src/service/configuration.service.ts b/src/ui_ng/lib/src/service/configuration.service.ts index 18ae26c1d..2137bae8c 100644 --- a/src/ui_ng/lib/src/service/configuration.service.ts +++ b/src/ui_ng/lib/src/service/configuration.service.ts @@ -3,8 +3,8 @@ import { Injectable, Inject } from "@angular/core"; import 'rxjs/add/observable/of'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { Http } from '@angular/http'; -import { HTTP_JSON_OPTIONS } from '../utils'; -import { Configuration } from '../config/config' +import { HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from '../utils'; +import { Configuration } from '../config/config'; /** @@ -51,7 +51,7 @@ export class ConfigurationDefaultService extends ConfigurationService { } getConfigurations(): Observable | Promise | Configuration { - return this.http.get(this._baseUrl, HTTP_JSON_OPTIONS).toPromise() + return this.http.get(this._baseUrl, HTTP_GET_OPTIONS).toPromise() .then(response => response.json() as Configuration) .catch(error => Promise.reject(error)); } diff --git a/src/ui_ng/lib/src/service/endpoint.service.ts b/src/ui_ng/lib/src/service/endpoint.service.ts index 66d797349..6eb6090a8 100644 --- a/src/ui_ng/lib/src/service/endpoint.service.ts +++ b/src/ui_ng/lib/src/service/endpoint.service.ts @@ -7,7 +7,7 @@ import 'rxjs/add/observable/of'; import { IServiceConfig, SERVICE_CONFIG } from '../service.config'; -import { buildHttpRequestOptions } from '../utils'; +import {buildHttpRequestOptions, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from '../utils'; /** * Define the service methods to handle the endpoint related things. @@ -136,7 +136,7 @@ export class EndpointDefaultService extends EndpointService { } let requestUrl: string = `${this._endpointUrl}/${endpointId}`; return this.http - .get(requestUrl) + .get(requestUrl, HTTP_GET_OPTIONS) .toPromise() .then(response=>response.json() as Endpoint) .catch(error=>Promise.reject(error)); @@ -148,7 +148,7 @@ export class EndpointDefaultService extends EndpointService { } let requestUrl: string = `${this._endpointUrl}`; return this.http - .post(requestUrl, JSON.stringify(endpoint)) + .post(requestUrl, JSON.stringify(endpoint), HTTP_JSON_OPTIONS) .toPromise() .then(response=>response.status) .catch(error=>Promise.reject(error)); @@ -163,7 +163,7 @@ export class EndpointDefaultService extends EndpointService { } let requestUrl: string = `${this._endpointUrl}/${endpointId}`; return this.http - .put(requestUrl, JSON.stringify(endpoint)) + .put(requestUrl, JSON.stringify(endpoint), HTTP_JSON_OPTIONS) .toPromise() .then(response=>response.status) .catch(error=>Promise.reject(error)); @@ -189,14 +189,14 @@ export class EndpointDefaultService extends EndpointService { if(endpoint.id) { requestUrl = `${this._endpointUrl}/${endpoint.id}/ping`; return this.http - .post(requestUrl, {}) + .post(requestUrl, HTTP_JSON_OPTIONS) .toPromise() .then(response=>response.status) .catch(error=>Promise.reject(error)); } else { requestUrl = `${this._endpointUrl}/ping`; return this.http - .post(requestUrl, endpoint) + .post(requestUrl, endpoint, HTTP_JSON_OPTIONS) .toPromise() .then(response=>response.status) .catch(error=>Promise.reject(error)); @@ -209,7 +209,7 @@ export class EndpointDefaultService extends EndpointService { } let requestUrl: string = `${this._endpointUrl}/${endpointId}/policies`; return this.http - .get(requestUrl) + .get(requestUrl, HTTP_GET_OPTIONS) .toPromise() .then(response=>response.json() as ReplicationRule[]) .catch(error=>Promise.reject(error)); diff --git a/src/ui_ng/lib/src/service/job-log.service.ts b/src/ui_ng/lib/src/service/job-log.service.ts index f35f253ca..c912879a9 100644 --- a/src/ui_ng/lib/src/service/job-log.service.ts +++ b/src/ui_ng/lib/src/service/job-log.service.ts @@ -5,7 +5,7 @@ import { Injectable, Inject } from "@angular/core"; import 'rxjs/add/observable/of'; import { Http, RequestOptions } from '@angular/http'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; -import { buildHttpRequestOptions, HTTP_JSON_OPTIONS } from '../utils'; +import { buildHttpRequestOptions, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from '../utils'; /** * Define the service methods to handle the job log related things. @@ -53,7 +53,7 @@ export class JobLogDefaultService extends JobLogService { } _getJobLog(logUrl: string): Observable | Promise | string { - return this.http.get(logUrl).toPromise() + return this.http.get(logUrl, HTTP_GET_OPTIONS).toPromise() .then(response => response.text()) .catch(error => Promise.reject(error)); } diff --git a/src/ui_ng/lib/src/service/project.service.ts b/src/ui_ng/lib/src/service/project.service.ts index 567546205..6220b7574 100644 --- a/src/ui_ng/lib/src/service/project.service.ts +++ b/src/ui_ng/lib/src/service/project.service.ts @@ -6,6 +6,7 @@ import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { Project } from '../project-policy-config/project'; import { ProjectPolicy } from '../project-policy-config/project-policy-config.component'; +import {HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from "../utils"; /** * Define the service methods to handle the Prject related things. @@ -49,9 +50,6 @@ export abstract class ProjectService { @Injectable() export class ProjectDefaultService extends ProjectService { - headers = new Headers({'Content-type': 'application/json'}); - options = new RequestOptions({'headers': this.headers}); - constructor( private http: Http, @Inject(SERVICE_CONFIG) private config: IServiceConfig @@ -65,7 +63,7 @@ export class ProjectDefaultService extends ProjectService { } return this.http - .get(`/api/projects/${projectId}`) + .get(`/api/projects/${projectId}`, HTTP_GET_OPTIONS) .map(response => response.json()) .catch(error => Observable.throw(error)); } @@ -78,7 +76,7 @@ export class ProjectDefaultService extends ProjectService { 'prevent_vul': projectPolicy.PreventVulImg ? 'true' : 'false', 'severity': projectPolicy.PreventVulImgServerity, 'auto_scan': projectPolicy.ScanImgOnPush ? 'true' : 'false' - } }, this.options) + } }, HTTP_JSON_OPTIONS) .map(response => response.status) .catch(error => Observable.throw(error)); } diff --git a/src/ui_ng/lib/src/service/replication.service.ts b/src/ui_ng/lib/src/service/replication.service.ts index 791340cdd..9d722798c 100644 --- a/src/ui_ng/lib/src/service/replication.service.ts +++ b/src/ui_ng/lib/src/service/replication.service.ts @@ -5,7 +5,7 @@ import { Injectable, Inject } from "@angular/core"; import 'rxjs/add/observable/of'; import { Http, RequestOptions } from '@angular/http'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; -import { buildHttpRequestOptions, HTTP_JSON_OPTIONS } from '../utils'; +import { buildHttpRequestOptions, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from '../utils'; /** * Define the service methods to handle the replication (rule and job) related things. @@ -179,7 +179,7 @@ export class ReplicationDefaultService extends ReplicationService { } let url: string = `${this._ruleBaseUrl}/${ruleId}`; - return this.http.get(url).toPromise() + return this.http.get(url, HTTP_GET_OPTIONS).toPromise() .then(response => response.json() as ReplicationRule) .catch(error => Promise.reject(error)); } @@ -281,7 +281,7 @@ export class ReplicationDefaultService extends ReplicationService { } let logUrl: string = `${this._jobBaseUrl}/${jobId}/log`; - return this.http.get(logUrl).toPromise() + return this.http.get(logUrl, HTTP_GET_OPTIONS).toPromise() .then(response => response.text()) .catch(error => Promise.reject(error)); } diff --git a/src/ui_ng/lib/src/service/scanning.service.ts b/src/ui_ng/lib/src/service/scanning.service.ts index ab4e2c035..7c297211a 100644 --- a/src/ui_ng/lib/src/service/scanning.service.ts +++ b/src/ui_ng/lib/src/service/scanning.service.ts @@ -101,13 +101,13 @@ export class ScanningResultDefaultService extends ScanningResultService { return Promise.reject('Bad argument'); } - return this.http.post(`${this._baseUrl}/${repoName}/tags/${tagId}/scan`, null).toPromise() + return this.http.post(`${this._baseUrl}/${repoName}/tags/${tagId}/scan`, HTTP_JSON_OPTIONS).toPromise() .then(() => { return true }) .catch(error => Promise.reject(error)); } startScanningAll(): Observable | Promise | any { - return this.http.post(`${this._baseUrl}/scanAll`,{}).toPromise() + return this.http.post(`${this._baseUrl}/scanAll`, HTTP_JSON_OPTIONS).toPromise() .then(() => {return true}) .catch(error => Promise.reject(error)); } diff --git a/src/ui_ng/lib/src/service/system-info.service.ts b/src/ui_ng/lib/src/service/system-info.service.ts index a167c6e8b..9e60dfd47 100644 --- a/src/ui_ng/lib/src/service/system-info.service.ts +++ b/src/ui_ng/lib/src/service/system-info.service.ts @@ -3,6 +3,7 @@ import { Http } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import { SystemInfo } from './interface'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; +import {HTTP_GET_OPTIONS} from "../utils"; /** * Get System information about current backend server. * @abstract @@ -26,7 +27,7 @@ export class SystemInfoDefaultService extends SystemInfoService { } getSystemInfo(): Observable | Promise | SystemInfo { let url = this.config.systemInfoEndpoint ? this.config.systemInfoEndpoint : '/api/systeminfo'; - return this.http.get(url) + return this.http.get(url, HTTP_GET_OPTIONS) .toPromise() .then(systemInfo=>systemInfo.json() as SystemInfo) .catch(error=>Promise.reject(error)); diff --git a/src/ui_ng/lib/src/service/tag.service.ts b/src/ui_ng/lib/src/service/tag.service.ts index 6242bcfc7..c958d8502 100644 --- a/src/ui_ng/lib/src/service/tag.service.ts +++ b/src/ui_ng/lib/src/service/tag.service.ts @@ -5,7 +5,7 @@ import { Injectable, Inject } from "@angular/core"; import 'rxjs/add/observable/of'; import { Http } from '@angular/http'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; -import { buildHttpRequestOptions, HTTP_JSON_OPTIONS } from '../utils'; +import { buildHttpRequestOptions, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from '../utils'; /** * For getting tag signatures. @@ -103,7 +103,7 @@ export class TagDefaultService extends TagService { _getSignatures(repositoryName: string): Promise { let url: string = `${this._baseUrl}/${repositoryName}/signatures`; - return this.http.get(url, HTTP_JSON_OPTIONS).toPromise() + return this.http.get(url, HTTP_GET_OPTIONS).toPromise() .then(response => response.json() as VerifiedSignature[]) .catch(error => Promise.reject(error)) } @@ -132,7 +132,7 @@ export class TagDefaultService extends TagService { } let url: string = `${this._baseUrl}/${repositoryName}/tags/${tag}`; - return this.http.get(url, HTTP_JSON_OPTIONS).toPromise() + return this.http.get(url, HTTP_GET_OPTIONS).toPromise() .then(response => response.json() as Tag) .catch(error => Promise.reject(error)); } diff --git a/src/ui_ng/lib/src/utils.ts b/src/ui_ng/lib/src/utils.ts index df76b4d38..92b5a526f 100644 --- a/src/ui_ng/lib/src/utils.ts +++ b/src/ui_ng/lib/src/utils.ts @@ -48,6 +48,15 @@ export const HTTP_JSON_OPTIONS: RequestOptions = new RequestOptions({ }) }); +export const HTTP_GET_OPTIONS: RequestOptions = new RequestOptions({ + headers: new Headers({ + "Content-Type": 'application/json', + "Accept": 'application/json', + "Cache-Control": 'no-cache', + "Pragma": 'no-cache' + }) +}); + /** * Build http request options * @@ -59,7 +68,9 @@ export function buildHttpRequestOptions(params: RequestQueryParams): RequestOpti let reqOptions: RequestOptions = new RequestOptions({ headers: new Headers({ "Content-Type": 'application/json', - "Accept": 'application/json' + "Accept": 'application/json', + "Cache-Control": 'no-cache', + "Pragma": 'no-cache' }) }); diff --git a/src/ui_ng/package.json b/src/ui_ng/package.json index e690d16d1..d3c626567 100644 --- a/src/ui_ng/package.json +++ b/src/ui_ng/package.json @@ -31,7 +31,7 @@ "clarity-icons": "^0.9.8", "clarity-ui": "^0.9.8", "core-js": "^2.4.1", - "harbor-ui": "0.4.97", + "harbor-ui": "0.5.0", "intl": "^1.2.5", "mutationobserver-shim": "^0.3.2", "ngx-cookie": "^1.0.0", diff --git a/src/ui_ng/src/app/account/password/password-setting.service.ts b/src/ui_ng/src/app/account/password/password-setting.service.ts index 5247109b5..42fb86cab 100644 --- a/src/ui_ng/src/app/account/password/password-setting.service.ts +++ b/src/ui_ng/src/app/account/password/password-setting.service.ts @@ -16,6 +16,7 @@ import { Headers, Http, RequestOptions, URLSearchParams } from '@angular/http'; import 'rxjs/add/operator/toPromise'; import { PasswordSetting } from './password-setting'; +import {HTTP_FORM_OPTIONS, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from "../../shared/shared.utils"; const passwordChangeEndpoint = "/api/users/:user_id/password"; const sendEmailEndpoint = "/sendEmail"; @@ -23,13 +24,6 @@ const resetPasswordEndpoint = "/reset"; @Injectable() export class PasswordSettingService { - headers: Headers = new Headers({ - "Accept": 'application/json', - "Content-Type": 'application/json' - }); - options: RequestOptions = new RequestOptions({ - 'headers': this.headers - }); constructor(private http: Http) { } @@ -39,7 +33,7 @@ export class PasswordSettingService { } let putUrl = passwordChangeEndpoint.replace(":user_id", userId + ""); - return this.http.put(putUrl, JSON.stringify(setting), this.options) + return this.http.put(putUrl, JSON.stringify(setting), HTTP_JSON_OPTIONS) .toPromise() .then(() => null) .catch(error => { @@ -53,7 +47,7 @@ export class PasswordSettingService { } let getUrl = sendEmailEndpoint + "?email=" + email; - return this.http.get(getUrl, this.options).toPromise() + return this.http.get(getUrl, HTTP_GET_OPTIONS).toPromise() .then(response => response) .catch(error => { return Promise.reject(error); @@ -65,18 +59,12 @@ export class PasswordSettingService { return Promise.reject("Invalid reset uuid or password"); } - let formHeaders = new Headers({ - "Content-Type": 'application/x-www-form-urlencoded' - }); - let formOptions: RequestOptions = new RequestOptions({ - headers: formHeaders - }); let body: URLSearchParams = new URLSearchParams(); body.set("reset_uuid", uuid); body.set("password", newPassword); - return this.http.post(resetPasswordEndpoint, body.toString(), formOptions) + return this.http.post(resetPasswordEndpoint, body.toString(), HTTP_FORM_OPTIONS) .toPromise() .then(response => response) .catch(error => { diff --git a/src/ui_ng/src/app/account/sign-in/sign-in.service.ts b/src/ui_ng/src/app/account/sign-in/sign-in.service.ts index b99dee504..1a233bc98 100644 --- a/src/ui_ng/src/app/account/sign-in/sign-in.service.ts +++ b/src/ui_ng/src/app/account/sign-in/sign-in.service.ts @@ -16,6 +16,7 @@ import { Headers, Http, URLSearchParams } from '@angular/http'; import 'rxjs/add/operator/toPromise'; import { SignInCredential } from '../../shared/sign-in-credential'; +import {HTTP_FORM_OPTIONS} from "../../shared/shared.utils"; const signInUrl = '/login'; /** @@ -27,9 +28,6 @@ const signInUrl = '/login'; */ @Injectable() export class SignInService { - headers = new Headers({ - "Content-Type": 'application/x-www-form-urlencoded' - }); constructor(private http: Http) {} @@ -46,7 +44,7 @@ export class SignInService { body.set('password', signInCredential.password); //Trigger Http - return this.http.post(signInUrl, body.toString(), { headers: this.headers }) + return this.http.post(signInUrl, body.toString(), HTTP_FORM_OPTIONS) .toPromise() .then(()=>null) .catch(this.handleError); diff --git a/src/ui_ng/src/app/app-config.service.ts b/src/ui_ng/src/app/app-config.service.ts index 0d73856b0..28c9c0e6a 100644 --- a/src/ui_ng/src/app/app-config.service.ts +++ b/src/ui_ng/src/app/app-config.service.ts @@ -18,7 +18,7 @@ import 'rxjs/add/operator/toPromise'; import { AppConfig } from './app-config'; import { CookieService } from 'ngx-cookie'; import { CookieKeyOfAdmiral, HarborQueryParamKey } from './shared/shared.const'; -import { maintainUrlQueryParmas } from './shared/shared.utils'; +import {HTTP_JSON_OPTIONS, maintainUrlQueryParmas, HTTP_GET_OPTIONS} from './shared/shared.utils'; export const systemInfoEndpoint = "/api/systeminfo"; /** @@ -30,12 +30,6 @@ export const systemInfoEndpoint = "/api/systeminfo"; */ @Injectable() export class AppConfigService { - headers = new Headers({ - "Content-Type": 'application/json' - }); - options = new RequestOptions({ - headers: this.headers - }); //Store the application configuration configurations: AppConfig = new AppConfig(); @@ -45,7 +39,7 @@ export class AppConfigService { private cookie: CookieService) { } public load(): Promise { - return this.http.get(systemInfoEndpoint, this.options).toPromise() + return this.http.get(systemInfoEndpoint, HTTP_GET_OPTIONS).toPromise() .then(response => { this.configurations = response.json() as AppConfig; @@ -90,7 +84,7 @@ export class AppConfigService { } //Save back to cookie - this.cookie.put(CookieKeyOfAdmiral, endpoint); + this.cookie.put(CookieKeyOfAdmiral, endpoint, HTTP_JSON_OPTIONS); this.configurations.admiral_endpoint = endpoint; } } \ No newline at end of file diff --git a/src/ui_ng/src/app/base/global-search/global-search.service.ts b/src/ui_ng/src/app/base/global-search/global-search.service.ts index e3a81b284..138702432 100644 --- a/src/ui_ng/src/app/base/global-search/global-search.service.ts +++ b/src/ui_ng/src/app/base/global-search/global-search.service.ts @@ -16,6 +16,7 @@ import { Headers, Http, RequestOptions } from '@angular/http'; import 'rxjs/add/operator/toPromise'; import { SearchResults } from './search-results'; +import {HTTP_GET_OPTIONS} from "../../shared/shared.utils"; const searchEndpoint = "/api/search"; /** @@ -27,12 +28,6 @@ const searchEndpoint = "/api/search"; */ @Injectable() export class GlobalSearchService { - headers = new Headers({ - "Content-Type": 'application/json' - }); - options = new RequestOptions({ - headers: this.headers - }); constructor(private http: Http) { } @@ -47,7 +42,7 @@ export class GlobalSearchService { doSearch(term: string): Promise { let searchUrl = searchEndpoint + "?q=" + term; - return this.http.get(searchUrl, this.options).toPromise() + return this.http.get(searchUrl, HTTP_GET_OPTIONS).toPromise() .then(response => response.json() as SearchResults) .catch(error => Promise.reject(error)); } diff --git a/src/ui_ng/src/app/config/config.service.ts b/src/ui_ng/src/app/config/config.service.ts index acc380777..e95050390 100644 --- a/src/ui_ng/src/app/config/config.service.ts +++ b/src/ui_ng/src/app/config/config.service.ts @@ -16,6 +16,7 @@ import { Headers, Http, RequestOptions } from '@angular/http'; import 'rxjs/add/operator/toPromise'; import { Configuration } from 'harbor-ui'; +import {HTTP_GET_OPTIONS, HTTP_JSON_OPTIONS} from "../shared/shared.utils"; const configEndpoint = "/api/configurations"; const emailEndpoint = "/api/email/ping"; @@ -23,38 +24,31 @@ const ldapEndpoint = "/api/ldap/ping"; @Injectable() export class ConfigurationService { - headers: Headers = new Headers({ - "Accept": 'application/json', - "Content-Type": 'application/json' - }); - options: RequestOptions = new RequestOptions({ - 'headers': this.headers - }); constructor(private http: Http) { } public getConfiguration(): Promise { - return this.http.get(configEndpoint, this.options).toPromise() + return this.http.get(configEndpoint, HTTP_GET_OPTIONS).toPromise() .then(response => response.json() as Configuration) .catch(error => Promise.reject(error)); } public saveConfiguration(values: any): Promise { - return this.http.put(configEndpoint, JSON.stringify(values), this.options) + return this.http.put(configEndpoint, JSON.stringify(values), HTTP_JSON_OPTIONS) .toPromise() .then(response => response) .catch(error => Promise.reject(error)); } public testMailServer(mailSettings: any): Promise { - return this.http.post(emailEndpoint, JSON.stringify(mailSettings), this.options) + return this.http.post(emailEndpoint, JSON.stringify(mailSettings), HTTP_JSON_OPTIONS) .toPromise() .then(response => response) .catch(error => Promise.reject(error)); } public testLDAPServer(ldapSettings: any): Promise { - return this.http.post(ldapEndpoint, JSON.stringify(ldapSettings), this.options) + return this.http.post(ldapEndpoint, JSON.stringify(ldapSettings), HTTP_JSON_OPTIONS) .toPromise() .then(response => response) .catch(error => Promise.reject(error)); diff --git a/src/ui_ng/src/app/log/audit-log.service.ts b/src/ui_ng/src/app/log/audit-log.service.ts index 6cde48bda..f6c0852e1 100644 --- a/src/ui_ng/src/app/log/audit-log.service.ts +++ b/src/ui_ng/src/app/log/audit-log.service.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. import { Injectable } from '@angular/core'; -import { Http, Headers, RequestOptions, URLSearchParams } from '@angular/http'; +import { Http, URLSearchParams } from '@angular/http'; import { AuditLog } from './audit-log'; @@ -20,45 +20,43 @@ import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/map'; import 'rxjs/add/observable/throw'; +import {buildHttpRequestOptions} from '../shared/shared.utils'; +import {RequestQueryParams} from 'harbor-ui'; -export const logEndpoint = "/api/logs"; +export const logEndpoint = '/api/logs'; @Injectable() export class AuditLogService { - httpOptions = new RequestOptions({ - headers: new Headers({ - "Content-Type": 'application/json', - "Accept": 'application/json' - }) - }); constructor(private http: Http) {} listAuditLogs(queryParam: AuditLog): Observable { let params: URLSearchParams = new URLSearchParams(queryParam.keywords); - if(queryParam.begin_timestamp) { - params.set('begin_timestamp', queryParam.begin_timestamp); + if (queryParam.begin_timestamp) { + params.set('begin_timestamp', queryParam.begin_timestamp); } - if(queryParam.end_timestamp) { + if (queryParam.end_timestamp) { params.set('end_timestamp', queryParam.end_timestamp); } - if(queryParam.username) { + if (queryParam.username) { params.set('username', queryParam.username); } - if(queryParam.page) { + if (queryParam.page) { params.set('page', queryParam.page); } - if(queryParam.page_size) { + if (queryParam.page_size) { params.set('page_size', queryParam.page_size); } return this.http - .get(`/api/projects/${queryParam.project_id}/logs`, {params: params}) + .get(`/api/projects/${queryParam.project_id}/logs`, buildHttpRequestOptions(params)) .map(response => response) .catch(error => Observable.throw(error)); } getRecentLogs(lines: number): Observable { - return this.http.get(logEndpoint + "?page_size=" + lines, this.httpOptions) + let params: RequestQueryParams = new RequestQueryParams(); + params.set('page_size', '' + lines); + return this.http.get(logEndpoint, buildHttpRequestOptions(params)) .map(response => response.json() as AuditLog[]) .catch(error => Observable.throw(error)); } diff --git a/src/ui_ng/src/app/project/member/member.service.ts b/src/ui_ng/src/app/project/member/member.service.ts index 33e7b0537..e868afd2c 100644 --- a/src/ui_ng/src/app/project/member/member.service.ts +++ b/src/ui_ng/src/app/project/member/member.service.ts @@ -20,6 +20,7 @@ import 'rxjs/add/operator/map'; import 'rxjs/add/observable/throw'; import { Member } from './member'; +import {HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from "../../shared/shared.utils"; @Injectable() export class MemberService { @@ -28,21 +29,21 @@ export class MemberService { listMembers(projectId: number, username: string): Observable { return this.http - .get(`/api/projects/${projectId}/members?username=${username}`) + .get(`/api/projects/${projectId}/members?username=${username}`, HTTP_GET_OPTIONS) .map(response=>response.json() as Member[]) .catch(error=>Observable.throw(error)); } addMember(projectId: number, username: string, roleId: number): Observable { return this.http - .post(`/api/projects/${projectId}/members`, { username: username, roles: [ roleId ] }) + .post(`/api/projects/${projectId}/members`, { username: username, roles: [ roleId ] }, HTTP_JSON_OPTIONS) .map(response=>response.status) .catch(error=>Observable.throw(error)); } changeMemberRole(projectId: number, userId: number, roleId: number): Observable { return this.http - .put(`/api/projects/${projectId}/members/${userId}`, { roles: [ roleId ]}) + .put(`/api/projects/${projectId}/members/${userId}`, { roles: [ roleId ]}, HTTP_JSON_OPTIONS) .map(response=>response.status) .catch(error=>Observable.throw(error)); } diff --git a/src/ui_ng/src/app/project/project.service.ts b/src/ui_ng/src/app/project/project.service.ts index 9948b279e..5c4236b6e 100644 --- a/src/ui_ng/src/app/project/project.service.ts +++ b/src/ui_ng/src/app/project/project.service.ts @@ -22,20 +22,16 @@ import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/map'; import 'rxjs/add/observable/throw'; - - +import {HTTP_JSON_OPTIONS, buildHttpRequestOptions, HTTP_GET_OPTIONS} from "../shared/shared.utils"; @Injectable() export class ProjectService { - - headers = new Headers({'Content-type': 'application/json'}); - options = new RequestOptions({'headers': this.headers}); constructor(private http: Http) {} getProject(projectId: number): Observable { return this.http - .get(`/api/projects/${projectId}`) + .get(`/api/projects/${projectId}`, HTTP_GET_OPTIONS) .map(response=>response.json()) .catch(error=>Observable.throw(error)); } @@ -52,8 +48,10 @@ export class ProjectService { if(isPublic !== undefined){ params.set('public', ''+isPublic); } + + //let options = new RequestOptions({ headers: this.getHeaders, search: params }); return this.http - .get(`/api/projects`, {search: params}) + .get(`/api/projects`, buildHttpRequestOptions(params)) .map(response=>response) .catch(error=>Observable.throw(error)); } @@ -64,14 +62,14 @@ export class ProjectService { JSON.stringify({'project_name': name, 'metadata': { public: metadata.public ? 'true' : 'false', }}) - , this.options) + , HTTP_JSON_OPTIONS) .map(response=>response.status) .catch(error=>Observable.throw(error)); } toggleProjectPublic(projectId: number, isPublic: string): Observable { return this.http - .put(`/api/projects/${projectId}`, { 'metadata': {'public': isPublic} }, this.options) + .put(`/api/projects/${projectId}`, { 'metadata': {'public': isPublic} }, HTTP_JSON_OPTIONS) .map(response => response.status) .catch(error => Observable.throw(error)); } @@ -92,7 +90,7 @@ export class ProjectService { checkProjectMember(projectId: number): Observable { return this.http - .get(`/api/projects/${projectId}/members`) + .get(`/api/projects/${projectId}/members`, HTTP_GET_OPTIONS) .map(response=>response.json()) .catch(error=>Observable.throw(error)); } diff --git a/src/ui_ng/src/app/repository/top-repo/top-repository.service.ts b/src/ui_ng/src/app/repository/top-repo/top-repository.service.ts index 684e7ae8c..50d32e759 100644 --- a/src/ui_ng/src/app/repository/top-repo/top-repository.service.ts +++ b/src/ui_ng/src/app/repository/top-repo/top-repository.service.ts @@ -16,6 +16,7 @@ import { Headers, Http, RequestOptions } from '@angular/http'; import 'rxjs/add/operator/toPromise'; import { Repository } from 'harbor-ui'; +import {HTTP_GET_OPTIONS} from "../../shared/shared.utils"; export const topRepoEndpoint = "/api/repositories/top"; /** @@ -27,12 +28,6 @@ export const topRepoEndpoint = "/api/repositories/top"; */ @Injectable() export class TopRepoService { - headers = new Headers({ - "Content-Type": 'application/json' - }); - options = new RequestOptions({ - headers: this.headers - }); constructor(private http: Http) { } @@ -45,7 +40,7 @@ export class TopRepoService { * @memberOf GlobalSearchService */ getTopRepos(): Promise { - return this.http.get(topRepoEndpoint, this.options).toPromise() + return this.http.get(topRepoEndpoint, HTTP_GET_OPTIONS).toPromise() .then(response => response.json() as Repository[]) .catch(error => Promise.reject(error)); } diff --git a/src/ui_ng/src/app/shared/session.service.ts b/src/ui_ng/src/app/shared/session.service.ts index b6ba8755c..bdffbf504 100644 --- a/src/ui_ng/src/app/shared/session.service.ts +++ b/src/ui_ng/src/app/shared/session.service.ts @@ -19,7 +19,8 @@ import { SessionUser } from './session-user'; import { Member } from '../project/member/member'; import { SignInCredential } from './sign-in-credential'; -import { enLang } from '../shared/shared.const' +import { enLang } from '../shared/shared.const'; +import {HTTP_FORM_OPTIONS, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from "./shared.utils"; const signInUrl = '/login'; const currentUserEndpint = "/api/users/current"; @@ -44,13 +45,9 @@ export class SessionService { projectMembers: Member[]; - headers = new Headers({ - "Content-Type": 'application/json' - }); - - formHeaders = new Headers({ + /*formHeaders = new Headers({ "Content-Type": 'application/x-www-form-urlencoded' - }); + });*/ constructor(private http: Http) { } @@ -72,7 +69,7 @@ export class SessionService { '&password=' + encodeURIComponent(signInCredential.password); //Trigger Http - return this.http.post(signInUrl, queryParam, { headers: this.formHeaders }) + return this.http.post(signInUrl, queryParam, HTTP_FORM_OPTIONS) .toPromise() .then(() => null) .catch(error => this.handleError(error)); @@ -86,7 +83,7 @@ export class SessionService { * @memberOf SessionService */ retrieveUser(): Promise { - return this.http.get(currentUserEndpint, { headers: this.headers }).toPromise() + return this.http.get(currentUserEndpint, HTTP_GET_OPTIONS).toPromise() .then(response => this.currentUser = response.json() as SessionUser) .catch(error => this.handleError(error)) } @@ -102,7 +99,7 @@ export class SessionService { * Log out the system */ signOff(): Promise { - return this.http.get(signOffEndpoint, { headers: this.headers }).toPromise() + return this.http.get(signOffEndpoint, HTTP_GET_OPTIONS).toPromise() .then(() => { //Destroy current session cache //this.currentUser = null; @@ -124,7 +121,7 @@ export class SessionService { return Promise.reject("Invalid account settings"); } let putUrl = accountEndpoint.replace(":id", account.user_id + ""); - return this.http.put(putUrl, JSON.stringify(account), { headers: this.headers }).toPromise() + return this.http.put(putUrl, JSON.stringify(account), HTTP_JSON_OPTIONS).toPromise() .then(() => { //Retrieve current session user return this.retrieveUser(); @@ -146,7 +143,7 @@ export class SessionService { } let getUrl = langEndpoint + "?lang=" + backendLang; - return this.http.get(getUrl).toPromise() + return this.http.get(getUrl, HTTP_GET_OPTIONS).toPromise() .then(() => null) .catch(error => this.handleError(error)) } @@ -158,7 +155,7 @@ export class SessionService { body.set('value', value); //Trigger Http - return this.http.post(userExistsEndpoint, body.toString(), { headers: this.formHeaders }) + return this.http.post(userExistsEndpoint, body.toString(), HTTP_FORM_OPTIONS) .toPromise() .then(response => { return response.json(); diff --git a/src/ui_ng/src/app/shared/shared.utils.ts b/src/ui_ng/src/app/shared/shared.utils.ts index 6f04d8542..c34dc29a5 100644 --- a/src/ui_ng/src/app/shared/shared.utils.ts +++ b/src/ui_ng/src/app/shared/shared.utils.ts @@ -15,6 +15,8 @@ import { NgForm } from '@angular/forms'; import { httpStatusCode, AlertType } from './shared.const'; import { MessageService } from '../global-message/message.service'; import { Comparator, State } from 'clarity-angular'; +import {RequestOptions, Headers} from "@angular/http"; +import {RequestQueryParams} from "harbor-ui"; /** * To handle the error message body @@ -155,6 +157,50 @@ export class CustomComparator implements Comparator { } } +export const HTTP_JSON_OPTIONS: RequestOptions = new RequestOptions({ + headers: new Headers({ + "Content-Type": 'application/json', + "Accept": 'application/json', + }) +}); +export const HTTP_GET_OPTIONS: RequestOptions = new RequestOptions({ + headers: new Headers({ + "Content-Type": 'application/json', + "Accept": 'application/json', + "Cache-Control": 'no-cache', + "Pragma": 'no-cache' + }) +}); + +export const HTTP_FORM_OPTIONS: RequestOptions = new RequestOptions({ + headers: new Headers({ + "Content-Type": 'application/x-www-form-urlencoded' + }) +}); +/** + * Build http request options + * + * @export + * @param {RequestQueryParams} params + * @returns {RequestOptions} + */ +export function buildHttpRequestOptions(params: RequestQueryParams): RequestOptions { + let reqOptions: RequestOptions = new RequestOptions({ + headers: new Headers({ + "Content-Type": 'application/json', + "Accept": 'application/json', + "Cache-Control": 'no-cache', + "Pragma": 'no-cache' + }) + }); + + if (params) { + reqOptions.search = params; + } + + return reqOptions; +} + /** * Filter columns via RegExp * diff --git a/src/ui_ng/src/app/shared/statictics/statistics.service.ts b/src/ui_ng/src/app/shared/statictics/statistics.service.ts index 874aaab69..451949814 100644 --- a/src/ui_ng/src/app/shared/statictics/statistics.service.ts +++ b/src/ui_ng/src/app/shared/statictics/statistics.service.ts @@ -17,6 +17,7 @@ import 'rxjs/add/operator/toPromise'; import { Statistics } from './statistics'; import { Volumes } from './volumes'; +import {HTTP_GET_OPTIONS} from "../shared.utils"; const statisticsEndpoint = "/api/statistics"; const volumesEndpoint = "/api/systeminfo/volumes"; @@ -29,23 +30,17 @@ const volumesEndpoint = "/api/systeminfo/volumes"; */ @Injectable() export class StatisticsService { - headers = new Headers({ - "Content-Type": 'application/json' - }); - options = new RequestOptions({ - headers: this.headers - }); constructor(private http: Http) { } getStatistics(): Promise { - return this.http.get(statisticsEndpoint, this.options).toPromise() + return this.http.get(statisticsEndpoint, HTTP_GET_OPTIONS).toPromise() .then(response => response.json() as Statistics) .catch(error => Promise.reject(error)); } getVolumes(): Promise { - return this.http.get(volumesEndpoint, this.options).toPromise() + return this.http.get(volumesEndpoint, HTTP_GET_OPTIONS).toPromise() .then(response => response.json() as Volumes) .catch(error => Promise.reject(error)); } diff --git a/src/ui_ng/src/app/user/user.service.ts b/src/ui_ng/src/app/user/user.service.ts index 6768a7c82..90a90c837 100644 --- a/src/ui_ng/src/app/user/user.service.ts +++ b/src/ui_ng/src/app/user/user.service.ts @@ -16,6 +16,7 @@ import { Headers, Http, RequestOptions } from '@angular/http'; import 'rxjs/add/operator/toPromise'; import { User } from './user'; +import {HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from "../shared/shared.utils"; const userMgmtEndpoint = '/api/users'; @@ -27,11 +28,6 @@ const userMgmtEndpoint = '/api/users'; */ @Injectable() export class UserService { - httpOptions = new RequestOptions({ - headers: new Headers({ - "Content-Type": 'application/json' - }) - }); constructor(private http: Http) { } @@ -42,21 +38,21 @@ export class UserService { //Get the user list getUsers(): Promise { - return this.http.get(userMgmtEndpoint, this.httpOptions).toPromise() + return this.http.get(userMgmtEndpoint, HTTP_GET_OPTIONS).toPromise() .then(response => response.json() as User[]) .catch(error => this.handleError(error)); } //Add new user addUser(user: User): Promise { - return this.http.post(userMgmtEndpoint, JSON.stringify(user), this.httpOptions).toPromise() + return this.http.post(userMgmtEndpoint, JSON.stringify(user), HTTP_JSON_OPTIONS).toPromise() .then(() => null) .catch(error => this.handleError(error)); } //Delete the specified user deleteUser(userId: number): Promise { - return this.http.delete(userMgmtEndpoint + "/" + userId, this.httpOptions) + return this.http.delete(userMgmtEndpoint + "/" + userId, HTTP_JSON_OPTIONS) .toPromise() .then(() => null) .catch(error => this.handleError(error)); @@ -64,7 +60,7 @@ export class UserService { //Update user to enable/disable the admin role updateUser(user: User): Promise { - return this.http.put(userMgmtEndpoint + "/" + user.user_id, JSON.stringify(user), this.httpOptions) + return this.http.put(userMgmtEndpoint + "/" + user.user_id, JSON.stringify(user), HTTP_JSON_OPTIONS) .toPromise() .then(() => null) .catch(error => this.handleError(error)); @@ -72,7 +68,7 @@ export class UserService { //Set user admin role updateUserRole(user: User): Promise { - return this.http.put(userMgmtEndpoint + "/" + user.user_id + "/sysadmin", JSON.stringify(user), this.httpOptions) + return this.http.put(userMgmtEndpoint + "/" + user.user_id + "/sysadmin", JSON.stringify(user), HTTP_JSON_OPTIONS) .toPromise() .then(() => null) .catch(error => this.handleError(error));