mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 10:45:45 +01:00
Merge branch 'master' of https://github.com/goharbor/harbor into project-quota-dev
This commit is contained in:
commit
2292954a31
@ -3478,6 +3478,37 @@ paths:
|
||||
description: The robot account is not found.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
'/system/oidc/ping':
|
||||
post:
|
||||
summary: Test the OIDC endpoint.
|
||||
description: Test the OIDC endpoint, the setting of the endpoint is provided in the request. This API can only
|
||||
be called by system admin.
|
||||
tags:
|
||||
- Products
|
||||
- System
|
||||
parameters:
|
||||
- name: endpoint
|
||||
in: body
|
||||
description: Request body for OIDC endpoint to be tested.
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
description: The URL of OIDC endpoint to be tested.
|
||||
verify_cert:
|
||||
type: boolean
|
||||
description: Whether the certificate should be verified
|
||||
responses:
|
||||
'200':
|
||||
description: Ping succeeded. The OIDC endpoint is valid.
|
||||
'400':
|
||||
description: The ping failed
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User does not have permission to call this API
|
||||
'/system/CVEWhitelist':
|
||||
get:
|
||||
summary: Get the system level whitelist of CVE.
|
||||
|
@ -1,16 +1,16 @@
|
||||
package chartserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
hlog "github.com/goharbor/harbor/src/common/utils/log"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -49,11 +49,13 @@ func NewChartClient(credential *Credential) *ChartClient { // Create http client
|
||||
func (cc *ChartClient) GetContent(addr string) ([]byte, error) {
|
||||
response, err := cc.sendRequest(addr, http.MethodGet, nil)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "get content failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Read response body error")
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
@ -61,6 +63,7 @@ func (cc *ChartClient) GetContent(addr string) ([]byte, error) {
|
||||
if response.StatusCode != http.StatusOK {
|
||||
text, err := extractError(content)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Extract content error failed")
|
||||
return nil, err
|
||||
}
|
||||
return nil, &commonhttp.Error{
|
||||
@ -106,7 +109,8 @@ func (cc *ChartClient) sendRequest(addr string, method string, body io.Reader) (
|
||||
|
||||
fullURI, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid url: %s", err.Error())
|
||||
err = errors.Wrap(err, "Invalid url")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request, err := http.NewRequest(method, addr, body)
|
||||
@ -121,7 +125,7 @@ func (cc *ChartClient) sendRequest(addr string, method string, body io.Reader) (
|
||||
|
||||
response, err := cc.httpClient.Do(request)
|
||||
if err != nil {
|
||||
hlog.Errorf("%s '%s' failed with error: %s", method, fullURI.Path, err)
|
||||
err = errors.Wrap(err, fmt.Sprintf("send request %s %s failed", method, fullURI.Path))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -2,19 +2,17 @@ package chartserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/replication"
|
||||
rep_event "github.com/goharbor/harbor/src/replication/event"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
"github.com/pkg/errors"
|
||||
helm_repo "k8s.io/helm/pkg/repo"
|
||||
|
||||
"os"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
// ListCharts gets the chart list under the namespace
|
||||
|
@ -20,12 +20,11 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/validation"
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
|
||||
"errors"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -144,11 +144,7 @@ func UpdateUserGroupName(id int, groupName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// OnBoardUserGroup will check if a usergroup exists in usergroup table, if not insert the usergroup and
|
||||
// put the id in the pointer of usergroup model, if it does exist, return the usergroup's profile.
|
||||
// This is used for ldap and uaa authentication, such the usergroup can have an ID in Harbor.
|
||||
// the keyAttribute and combinedKeyAttribute are key columns used to check duplicate usergroup in harbor
|
||||
func OnBoardUserGroup(g *models.UserGroup, keyAttribute string, combinedKeyAttributes ...string) error {
|
||||
func onBoardCommonUserGroup(g *models.UserGroup, keyAttribute string, combinedKeyAttributes ...string) error {
|
||||
g.LdapGroupDN = utils.TrimLower(g.LdapGroupDN)
|
||||
|
||||
o := dao.GetOrmer()
|
||||
@ -172,3 +168,12 @@ func OnBoardUserGroup(g *models.UserGroup, keyAttribute string, combinedKeyAttri
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnBoardUserGroup will check if a usergroup exists in usergroup table, if not insert the usergroup and
|
||||
// put the id in the pointer of usergroup model, if it does exist, return the usergroup's profile.
|
||||
func OnBoardUserGroup(g *models.UserGroup) error {
|
||||
if g.GroupType == common.LDAPGroupType {
|
||||
return onBoardCommonUserGroup(g, "LdapGroupDN", "GroupType")
|
||||
}
|
||||
return onBoardCommonUserGroup(g, "GroupName", "GroupType")
|
||||
}
|
||||
|
@ -256,7 +256,7 @@ func TestOnBoardUserGroup(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := OnBoardUserGroup(tt.args.g, "LdapGroupDN", "GroupType"); (err != nil) != tt.wantErr {
|
||||
if err := OnBoardUserGroup(tt.args.g); (err != nil) != tt.wantErr {
|
||||
t.Errorf("OnBoardUserGroup() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
|
@ -206,3 +206,19 @@ func RefreshToken(ctx context.Context, token *Token) (*Token, error) {
|
||||
}
|
||||
return &Token{Token: *t, IDToken: it}, nil
|
||||
}
|
||||
|
||||
// Conn wraps connection info of an OIDC endpoint
|
||||
type Conn struct {
|
||||
URL string `json:"url"`
|
||||
VerifyCert bool `json:"verify_cert"`
|
||||
}
|
||||
|
||||
// TestEndpoint tests whether the endpoint is a valid OIDC endpoint.
|
||||
// The nil return value indicates the success of the test
|
||||
func TestEndpoint(conn Conn) error {
|
||||
|
||||
// gooidc will try to call the discovery api when creating the provider and that's all we need to check
|
||||
ctx := clientCtx(context.Background(), conn.VerifyCert)
|
||||
_, err := gooidc.NewProvider(ctx, conn.URL)
|
||||
return err
|
||||
}
|
||||
|
@ -97,3 +97,16 @@ func TestAuthCodeURL(t *testing.T) {
|
||||
assert.Equal(t, "offline", q.Get("access_type"))
|
||||
assert.False(t, strings.Contains(q.Get("scope"), "offline_access"))
|
||||
}
|
||||
|
||||
func TestTestEndpoint(t *testing.T) {
|
||||
c1 := Conn{
|
||||
URL: googleEndpoint,
|
||||
VerifyCert: true,
|
||||
}
|
||||
c2 := Conn{
|
||||
URL: "https://www.baidu.com",
|
||||
VerifyCert: false,
|
||||
}
|
||||
assert.Nil(t, TestEndpoint(c1))
|
||||
assert.NotNil(t, TestEndpoint(c2))
|
||||
}
|
||||
|
@ -15,9 +15,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"errors"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/goharbor/harbor/src/common/api"
|
||||
"github.com/goharbor/harbor/src/common/security"
|
||||
|
@ -145,6 +145,7 @@ func init() {
|
||||
beego.Router("/api/system/gc/schedule", &GCAPI{}, "get:Get;put:Put;post:Post")
|
||||
beego.Router("/api/system/scanAll/schedule", &ScanAllAPI{}, "get:Get;put:Put;post:Post")
|
||||
beego.Router("/api/system/CVEWhitelist", &SysCVEWhitelistAPI{}, "get:Get;put:Put")
|
||||
beego.Router("/api/system/oidc/ping", &OIDCAPI{}, "post:Ping")
|
||||
|
||||
beego.Router("/api/projects/:pid([0-9]+)/robots/", &RobotAPI{}, "post:Post;get:List")
|
||||
beego.Router("/api/projects/:pid([0-9]+)/robots/:id([0-9]+)", &RobotAPI{}, "get:Get;put:Put;delete:Delete")
|
||||
|
56
src/core/api/oidc.go
Normal file
56
src/core/api/oidc.go
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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 api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/common/utils/oidc"
|
||||
)
|
||||
|
||||
// OIDCAPI handles the requests to /api/system/oidc/xxx
|
||||
type OIDCAPI struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
// Prepare validates the request initially
|
||||
func (oa *OIDCAPI) Prepare() {
|
||||
oa.BaseController.Prepare()
|
||||
if !oa.SecurityCtx.IsAuthenticated() {
|
||||
oa.SendUnAuthorizedError(errors.New("unauthorized"))
|
||||
return
|
||||
}
|
||||
if !oa.SecurityCtx.IsSysAdmin() {
|
||||
msg := "only system admin has permission to access this API"
|
||||
log.Errorf(msg)
|
||||
oa.SendForbiddenError(errors.New(msg))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Ping will handles the request to test connection to OIDC endpoint
|
||||
func (oa *OIDCAPI) Ping() {
|
||||
var c oidc.Conn
|
||||
if err := oa.DecodeJSONReq(&c); err != nil {
|
||||
log.Error("Failed to decode JSON request.")
|
||||
oa.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
if err := oidc.TestEndpoint(c); err != nil {
|
||||
log.Errorf("Failed to verify connection: %+v, err: %v", c, err)
|
||||
oa.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
}
|
69
src/core/api/oidc_test.go
Normal file
69
src/core/api/oidc_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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 api
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/utils/oidc"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOIDCAPI_Ping(t *testing.T) {
|
||||
url := "/api/system/oidc/ping"
|
||||
cases := []*codeCheckingCase{
|
||||
{ // 401
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
bodyJSON: oidc.Conn{},
|
||||
url: url,
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
{ // 403
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
bodyJSON: oidc.Conn{},
|
||||
url: url,
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
{ // 400
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
bodyJSON: oidc.Conn{
|
||||
URL: "https://www.baidu.com",
|
||||
VerifyCert: true,
|
||||
},
|
||||
url: url,
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
{ // 200
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
bodyJSON: oidc.Conn{
|
||||
URL: "https://accounts.google.com",
|
||||
VerifyCert: true,
|
||||
},
|
||||
url: url,
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
@ -18,6 +18,8 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
@ -27,10 +29,7 @@ import (
|
||||
errutil "github.com/goharbor/harbor/src/common/utils/error"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type deletableResp struct {
|
||||
@ -232,7 +231,10 @@ func (p *ProjectAPI) Get() {
|
||||
return
|
||||
}
|
||||
|
||||
p.populateProperties(p.project)
|
||||
err := p.populateProperties(p.project)
|
||||
if err != nil {
|
||||
log.Errorf("populate project poroperties failed with : %+v", err)
|
||||
}
|
||||
|
||||
p.Data["json"] = p.project
|
||||
p.ServeJSON()
|
||||
@ -402,15 +404,17 @@ func (p *ProjectAPI) List() {
|
||||
}
|
||||
|
||||
for _, project := range result.Projects {
|
||||
p.populateProperties(project)
|
||||
err = p.populateProperties(project)
|
||||
if err != nil {
|
||||
log.Errorf("populate project properties failed %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
p.SetPaginationHeader(result.Total, page, size)
|
||||
p.Data["json"] = result.Projects
|
||||
p.ServeJSON()
|
||||
}
|
||||
|
||||
func (p *ProjectAPI) populateProperties(project *models.Project) {
|
||||
func (p *ProjectAPI) populateProperties(project *models.Project) error {
|
||||
if p.SecurityCtx.IsAuthenticated() {
|
||||
roles := p.SecurityCtx.GetProjectRoles(project.ProjectID)
|
||||
if len(roles) != 0 {
|
||||
@ -427,9 +431,8 @@ func (p *ProjectAPI) populateProperties(project *models.Project) {
|
||||
ProjectIDs: []int64{project.ProjectID},
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed to get total of repositories of project %d: %v", project.ProjectID, err)
|
||||
p.SendInternalServerError(errors.New(""))
|
||||
return
|
||||
err = errors.Wrap(err, fmt.Sprintf("get repo count of project %d failed", project.ProjectID))
|
||||
return err
|
||||
}
|
||||
|
||||
project.RepoCount = total
|
||||
@ -438,13 +441,13 @@ func (p *ProjectAPI) populateProperties(project *models.Project) {
|
||||
if config.WithChartMuseum() {
|
||||
count, err := chartController.GetCountOfCharts([]string{project.Name})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get total of charts under project %s: %v", project.Name, err)
|
||||
p.SendInternalServerError(errors.New(""))
|
||||
return
|
||||
err = errors.Wrap(err, fmt.Sprintf("get chart count of project %d failed", project.ProjectID))
|
||||
return err
|
||||
}
|
||||
|
||||
project.ChartCount = count
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Put ...
|
||||
|
@ -17,6 +17,7 @@ package authproxy
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@ -190,12 +191,14 @@ func (a *Auth) SearchGroup(groupKey string) (*models.UserGroup, error) {
|
||||
// OnBoardGroup create user group entity in Harbor DB, altGroupName is not used.
|
||||
func (a *Auth) OnBoardGroup(u *models.UserGroup, altGroupName string) error {
|
||||
// if group name provided, on board the user group
|
||||
userGroup := &models.UserGroup{GroupName: u.GroupName, GroupType: common.HTTPGroupType}
|
||||
err := group.OnBoardUserGroup(u, "GroupName", "GroupType")
|
||||
if len(u.GroupName) == 0 {
|
||||
return errors.New("Should provide a group name")
|
||||
}
|
||||
u.GroupType = common.HTTPGroupType
|
||||
err := group.OnBoardUserGroup(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.ID = userGroup.ID
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,7 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
mockSvr = test.NewMockServer(map[string]string{"jt": "pp", "Admin@vsphere.local": "Admin!23"})
|
||||
defer mockSvr.Close()
|
||||
defer dao.ExecuteBatchSQL([]string{"delete from user_group where group_name='OnBoardTest'"})
|
||||
a = &Auth{
|
||||
Endpoint: mockSvr.URL + "/test/login",
|
||||
TokenReviewEndpoint: mockSvr.URL + "/test/tokenreview",
|
||||
@ -50,10 +51,17 @@ func TestMain(m *testing.M) {
|
||||
// So it won't require mocking the cfgManager
|
||||
settingTimeStamp: time.Now(),
|
||||
}
|
||||
cfgMap := cut.GetUnitTestConfig()
|
||||
conf := map[string]interface{}{
|
||||
common.HTTPAuthProxyEndpoint: a.Endpoint,
|
||||
common.HTTPAuthProxyTokenReviewEndpoint: a.TokenReviewEndpoint,
|
||||
common.HTTPAuthProxyVerifyCert: !a.SkipCertVerify,
|
||||
common.PostGreSQLSSLMode: cfgMap[common.PostGreSQLSSLMode],
|
||||
common.PostGreSQLUsername: cfgMap[common.PostGreSQLUsername],
|
||||
common.PostGreSQLPort: cfgMap[common.PostGreSQLPort],
|
||||
common.PostGreSQLHOST: cfgMap[common.PostGreSQLHOST],
|
||||
common.PostGreSQLPassword: cfgMap[common.PostGreSQLPassword],
|
||||
common.PostGreSQLDatabase: cfgMap[common.PostGreSQLDatabase],
|
||||
}
|
||||
|
||||
config.InitWithSettings(conf)
|
||||
@ -174,3 +182,19 @@ func TestAuth_PostAuthenticate(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAuth_OnBoardGroup(t *testing.T) {
|
||||
input := &models.UserGroup{
|
||||
GroupName: "OnBoardTest",
|
||||
GroupType: common.HTTPGroupType,
|
||||
}
|
||||
a.OnBoardGroup(input, "")
|
||||
|
||||
assert.True(t, input.ID > 0, "The OnBoardGroup should have a valid group ID")
|
||||
|
||||
emptyGroup := &models.UserGroup{}
|
||||
err := a.OnBoardGroup(emptyGroup, "")
|
||||
if err == nil {
|
||||
t.Fatal("Empty user group should failed to OnBoard")
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ func (l *Auth) OnBoardGroup(u *models.UserGroup, altGroupName string) error {
|
||||
if len(userGroupList) > 0 {
|
||||
return auth.ErrDuplicateLDAPGroup
|
||||
}
|
||||
return group.OnBoardUserGroup(u, "LdapGroupDN", "GroupType")
|
||||
return group.OnBoardUserGroup(u)
|
||||
}
|
||||
|
||||
// PostAuthenticate -- If user exist in harbor DB, sync email address, if not exist, call OnBoardUser
|
||||
|
@ -97,6 +97,7 @@ func initRouters() {
|
||||
beego.Router("/api/system/gc/schedule", &api.GCAPI{}, "get:Get;put:Put;post:Post")
|
||||
beego.Router("/api/system/scanAll/schedule", &api.ScanAllAPI{}, "get:Get;put:Put;post:Post")
|
||||
beego.Router("/api/system/CVEWhitelist", &api.SysCVEWhitelistAPI{}, "get:Get;put:Put")
|
||||
beego.Router("/api/system/oidc/ping", &api.OIDCAPI{}, "post:Ping")
|
||||
|
||||
beego.Router("/api/logs", &api.LogAPI{})
|
||||
|
||||
|
@ -21,18 +21,10 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/jobservice/common/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
proxyEnvHTTP = "http_proxy"
|
||||
proxyEnvHTTPS = "https_proxy"
|
||||
)
|
||||
|
||||
// Client for handling the hook events
|
||||
@ -60,19 +52,7 @@ func NewClient(ctx context.Context) Client {
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
// Get the http/https proxies
|
||||
proxyAddr, ok := os.LookupEnv(proxyEnvHTTP)
|
||||
if !ok {
|
||||
proxyAddr, ok = os.LookupEnv(proxyEnvHTTPS)
|
||||
}
|
||||
|
||||
if ok && !utils.IsEmptyStr(proxyAddr) {
|
||||
proxyURL, err := url.Parse(proxyAddr)
|
||||
if err == nil {
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
}
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
|
@ -99,6 +99,7 @@
|
||||
class="btn btn-link">{{'CVE_WHITELIST.ADD'|translate}}</button>
|
||||
</div>
|
||||
<div class="add-modal" *ngIf="showAddModal">
|
||||
<clr-icon (click)="showAddModal=false" class="float-lg-right margin-top-4" shape="window-close"></clr-icon>
|
||||
<div>
|
||||
<clr-textarea-container>
|
||||
<label>{{'CVE_WHITELIST.ENTER'|translate}}</label>
|
||||
|
@ -11,17 +11,17 @@
|
||||
</clr-tooltip>
|
||||
<clr-dropdown *ngIf="isClairDBFullyReady && showScanningNamespaces" class="clr-dropdown-override">
|
||||
<button class="btn btn-link btn-font" clrDropdownToggle>
|
||||
{{ updatedTimestamp | date:'MM/dd/y HH:mm:ss' }} AM
|
||||
{{ updatedTimestamp | date:'short' }}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu [clrPosition]="'bottom-right'" class="dropdown-namespace">
|
||||
<div *ngFor="let nt of namespaceTimestamps" class="namespace">
|
||||
<span class="label label-info">{{nt.namespace}}</span>
|
||||
<span>{{ convertToLocalTime(nt.last_update) | date:'MM/dd/y HH:mm:ss'}} AM</span>
|
||||
<span>{{ convertToLocalTime(nt.last_update) | date:'short'}} </span>
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
<span *ngIf="isClairDBFullyReady && !showScanningNamespaces">{{ updatedTimestamp | date:'MM/dd/y HH:mm:ss' }} AM</span>
|
||||
<span *ngIf="isClairDBFullyReady && !showScanningNamespaces">{{ updatedTimestamp | date:'short' }} </span>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<cron-selection #CronScheduleComponent [labelCurrent]="getLabelCurrent" [labelEdit]='getLabelCurrent' [originCron]='originCron' (inputvalue)="scanAll($event)"></cron-selection>
|
||||
|
@ -109,6 +109,7 @@
|
||||
class="btn btn-link ml-1">{{'CVE_WHITELIST.ADD_SYSTEM'|translate}}</button>
|
||||
</div>
|
||||
<div class="add-modal" *ngIf="showAddModal && !isUseSystemWhitelist()">
|
||||
<clr-icon (click)="showAddModal=false" class="float-lg-right margin-top-4" shape="window-close"></clr-icon>
|
||||
<div>
|
||||
<clr-textarea-container>
|
||||
<label>{{'CVE_WHITELIST.ENTER'|translate}}</label>
|
||||
|
@ -44,42 +44,46 @@
|
||||
{{'ROBOT_ACCOUNT.PERMISSIONS' | translate}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="clr-col">
|
||||
<div class="form-group padding-left-120">
|
||||
<label>{{'ROBOT_ACCOUNT.PERMISSIONS_IMAGE' | translate}}</label>
|
||||
<div class="radio-inline">
|
||||
<input type="radio" name="image-permission"
|
||||
id="image-permission-pull"
|
||||
value="pull"
|
||||
[(ngModel)]="imagePermission">
|
||||
<label for="image-permission-pull">{{'ROBOT_ACCOUNT.PULL' | translate}}</label>
|
||||
</div>
|
||||
<div class="radio-inline">
|
||||
<input type="radio" name="image-permission"
|
||||
id="image-permission-push-and-pull"
|
||||
value="push-and-pull"
|
||||
[(ngModel)]="imagePermission">
|
||||
<label for="image-permission-push-and-pull">{{'ROBOT_ACCOUNT.PUSH' | translate}}
|
||||
& {{'ROBOT_ACCOUNT.PULL' | translate}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group padding-left-120">
|
||||
<label>{{'ROBOT_ACCOUNT.PERMISSIONS_HELMCHART' | translate}}</label>
|
||||
<div class="checkbox-inline">
|
||||
<input type="checkbox" id="helm-permission-push"
|
||||
[checked]="robot.access.isPushChart"
|
||||
[(ngModel)]="robot.access.isPushChart"
|
||||
name="helm-permission">
|
||||
<label for="helm-permission-push">{{'ROBOT_ACCOUNT.PUSH' | translate}}</label>
|
||||
</div>
|
||||
<div class="checkbox-inline">
|
||||
<input type="checkbox" id="helm-permission-pull"
|
||||
[checked]="robot.access.isPullChart"
|
||||
[(ngModel)]="robot.access.isPullChart"
|
||||
name="helm-permission">
|
||||
<label for="helm-permission-pull">{{'ROBOT_ACCOUNT.PULL' | translate}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-col p-0">
|
||||
<table class="table table-noborder m-0 w-90">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="left">{{'ROBOT_ACCOUNT.PUSH' | translate}}</th>
|
||||
<th class="left">{{'ROBOT_ACCOUNT.PULL' | translate}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left">
|
||||
<span>{{'ROBOT_ACCOUNT.PERMISSIONS_IMAGE' | translate}}</span>
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'ROBOT_ACCOUNT.PULL_IS_MUST' | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" name="image-permission-push"
|
||||
[(ngModel)]="imagePermissionPush" clrCheckbox>
|
||||
</td>
|
||||
<td class="clr-form-control-disabled">
|
||||
<input disabled type="checkbox" name="image-permission-pull"
|
||||
[(ngModel)]="imagePermissionPull" clrCheckbox>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left">{{'ROBOT_ACCOUNT.PERMISSIONS_HELMCHART' | translate}}</td>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
[(ngModel)]="robot.access.isPushChart"
|
||||
name="helm-permission" clrCheckbox>
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
[(ngModel)]="robot.access.isPullChart"
|
||||
name="helm-permission" clrCheckbox>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -42,5 +42,8 @@
|
||||
}
|
||||
|
||||
.padding-left-120{
|
||||
padding-left: 120px;
|
||||
padding-left: 126px;
|
||||
}
|
||||
.w-90{
|
||||
width: 90%;
|
||||
}
|
@ -38,7 +38,8 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
robotNameChecker: Subject<string> = new Subject<string>();
|
||||
nameTooltipText = "ROBOT_ACCOUNT.ROBOT_NAME";
|
||||
robotForm: NgForm;
|
||||
imagePermission: string = "push-and-pull";
|
||||
imagePermissionPush: boolean = true;
|
||||
imagePermissionPull: boolean = true;
|
||||
@Input() projectId: number;
|
||||
@Input() projectName: string;
|
||||
@Output() create = new EventEmitter<boolean>();
|
||||
@ -99,6 +100,8 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
this.robot.name = "";
|
||||
this.robot.description = "";
|
||||
this.addRobotOpened = true;
|
||||
this.imagePermissionPush = true;
|
||||
this.imagePermissionPull = true;
|
||||
this.isRobotNameValid = true;
|
||||
this.robot = new Robot();
|
||||
this.nameTooltipText = "ROBOT_ACCOUNT.ROBOT_NAME";
|
||||
@ -118,12 +121,12 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
// set value to robot.access.isPullImage and robot.access.isPushOrPullImage when submit
|
||||
if ( this.imagePermission === 'pull' ) {
|
||||
this.robot.access.isPullImage = true;
|
||||
this.robot.access.isPushOrPullImage = false;
|
||||
} else {
|
||||
if ( this.imagePermissionPush && this.imagePermissionPull) {
|
||||
this.robot.access.isPullImage = false;
|
||||
this.robot.access.isPushOrPullImage = true;
|
||||
} else {
|
||||
this.robot.access.isPullImage = true;
|
||||
this.robot.access.isPushOrPullImage = false;
|
||||
}
|
||||
this.isSubmitOnGoing = true;
|
||||
this.robotService
|
||||
|
@ -322,7 +322,8 @@
|
||||
"CREATED_SUCCESS": "Created '{{param}}' successfully.",
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "Confirm removal of robot accounts",
|
||||
"DELETION_SUMMARY": "Do you want to delete robot accounts {{param}}?"
|
||||
"DELETION_SUMMARY": "Do you want to delete robot accounts {{param}}?",
|
||||
"PULL_IS_MUST" : "Pull permission is checked by default and can not be modified."
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "Group",
|
||||
|
@ -323,7 +323,8 @@
|
||||
"CREATED_SUCCESS": "Created '{{param}}' successfully.",
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "Confirm removal of robot accounts",
|
||||
"DELETION_SUMMARY": "Do you want to delete robot accounts {{param}}?"
|
||||
"DELETION_SUMMARY": "Do you want to delete robot accounts {{param}}?",
|
||||
"PULL_IS_MUST" : "Pull permission is checked by default and can not be modified."
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "Group",
|
||||
|
@ -315,7 +315,8 @@
|
||||
"CREATED_SUCCESS": "Created '{{param}}' successfully.",
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "confirmer l'enlèvement des comptes du robot ",
|
||||
"DELETION_SUMMARY": "Voulez-vous supprimer la règle {{param}}?"
|
||||
"DELETION_SUMMARY": "Voulez-vous supprimer la règle {{param}}?",
|
||||
"PULL_IS_MUST" : "Pull permission is checked by default and can not be modified."
|
||||
},
|
||||
"GROUP": {
|
||||
"Group": "Group",
|
||||
|
@ -320,7 +320,8 @@
|
||||
"CREATED_SUCCESS": "Created '{{param}}' successfully.",
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "Confirmar a remoção do robô Contas",
|
||||
"DELETION_SUMMARY": "Você quer remover a regra {{param}}?"
|
||||
"DELETION_SUMMARY": "Você quer remover a regra {{param}}?",
|
||||
"PULL_IS_MUST" : "Pull permission is checked by default and can not be modified."
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "Grupo",
|
||||
|
@ -321,7 +321,8 @@
|
||||
"CREATED_SUCCESS": "创建账户 '{{param}}' 成功。",
|
||||
"COPY_SUCCESS": "成功复制 '{{param}}' 的令牌",
|
||||
"DELETION_TITLE": "删除账户确认",
|
||||
"DELETION_SUMMARY": "你确认删除机器人账户 {{param}}?"
|
||||
"DELETION_SUMMARY": "你确认删除机器人账户 {{param}}?",
|
||||
"PULL_IS_MUST" : "拉取权限默认选中且不可修改。"
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "组",
|
||||
|
Loading…
Reference in New Issue
Block a user