mirror of
https://github.com/goharbor/harbor.git
synced 2025-04-01 01:05:48 +02:00
fix code conflict and rebase with master
Signed-off-by: Steven Zou <szou@vmware.com>
This commit is contained in:
commit
370a364c29
.travis.ymlADOPTERS.mdCONTRIBUTING.mdMakefileOWNERS.mdRELEASES.mdSECURITY.mdVERSION
docs
make
src
common
core
pkg/scan/whitelist
portal
lib/src
src
app
account/account-settings
base/harbor-shell
config/scanner
config-scanner.component.htmlconfig-scanner.component.scssconfig-scanner.component.ts
new-scanner-form
project
member
robot-account/add-robot
scanner
summary
repository/tag-detail
shared
i18n/lang
10
.travis.yml
10
.travis.yml
@ -1,23 +1,23 @@
|
||||
sudo: true
|
||||
language: go
|
||||
go:
|
||||
- 1.12.5
|
||||
- 1.12.12
|
||||
go_import_path: github.com/goharbor/harbor
|
||||
services:
|
||||
- docker
|
||||
dist: trusty
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.12.5
|
||||
- go: 1.12.12
|
||||
env:
|
||||
- UTTEST=true
|
||||
- go: 1.12.5
|
||||
- go: 1.12.12
|
||||
env:
|
||||
- APITEST_DB=true
|
||||
- go: 1.12.5
|
||||
- go: 1.12.12
|
||||
env:
|
||||
- APITEST_LDAP=true
|
||||
- go: 1.12.5
|
||||
- go: 1.12.12
|
||||
env:
|
||||
- OFFLINE=true
|
||||
- language: node_js
|
||||
|
@ -80,3 +80,6 @@ feature within Harbor before deploying images into production.
|
||||
**Allegis:** Harbor is used at Allegis as a secure private registry to store
|
||||
and scan customized container images for different business applications, like
|
||||
ELK stack, as part of their CI/CD pipeline.
|
||||
|
||||
# Adding a logo
|
||||
If you would like to add your logo to the `Users and Partners of Harbor` section of the website, add a PNG version of your logo to the docs/img directory in this repo and submit a pull request with your change. Name the image file something that reflects your company (e.g., if your company is called Acme, name the image acme.png). We will follow up and make the change in the goharbor.io website as well.
|
||||
|
@ -127,6 +127,7 @@ Harbor backend is written in [Go](http://golang.org/). If you don't have a Harbo
|
||||
| 1.6 | 1.9.2 |
|
||||
| 1.7 | 1.9.2 |
|
||||
| 1.8 | 1.11.2 |
|
||||
| 1.9 | 1.12.12 |
|
||||
|
||||
Ensure your GOPATH and PATH have been configured in accordance with the Go environment instructions.
|
||||
|
||||
|
7
Makefile
7
Makefile
@ -9,7 +9,7 @@
|
||||
# compile_golangimage:
|
||||
# compile from golang image
|
||||
# for example: make compile_golangimage -e GOBUILDIMAGE= \
|
||||
# golang:1.11.2
|
||||
# golang:1.12.12
|
||||
# compile_core, compile_jobservice: compile specific binary
|
||||
#
|
||||
# build: build Harbor docker images from photon baseimage
|
||||
@ -111,6 +111,9 @@ CLAIRADAPTERVERSION=c7db8b15
|
||||
# version of chartmuseum
|
||||
CHARTMUSEUMVERSION=v0.9.0
|
||||
|
||||
# version of registry for pulling the source code
|
||||
REGISTRY_SRC_TAG=v2.7.1
|
||||
|
||||
define VERSIONS_FOR_PREPARE
|
||||
VERSION_TAG: $(VERSIONTAG)
|
||||
REGISTRY_VERSION: $(REGISTRYVERSION)
|
||||
@ -138,7 +141,7 @@ GOINSTALL=$(GOCMD) install
|
||||
GOTEST=$(GOCMD) test
|
||||
GODEP=$(GOTEST) -i
|
||||
GOFMT=gofmt -w
|
||||
GOBUILDIMAGE=golang:1.12.5
|
||||
GOBUILDIMAGE=golang:1.12.12
|
||||
GOBUILDPATH=/harbor
|
||||
GOIMAGEBUILDCMD=/usr/local/go/bin/go
|
||||
GOIMAGEBUILD=$(GOIMAGEBUILDCMD) build -mod vendor
|
||||
|
26
OWNERS.md
26
OWNERS.md
@ -3,28 +3,6 @@ guidelines.
|
||||
[GOVERNANCE.md](https://github.com/goharbor/community/blob/master/GOVERNANCE.md)
|
||||
describes governance guidelines and maintainer responsibilities.
|
||||
|
||||
Maintainer list here is a copy of [MAINTAINERS.md](https://github.com/goharbor/community/blob/master/MAINTAINERS.md).
|
||||
[MAINTAINERS.md](https://github.com/goharbor/community/blob/master/MAINTAINERS.md) contains the project maintainers.
|
||||
|
||||
## Core Maintainers
|
||||
|
||||
| Core Maintainer | GitHub ID | Affiliation |
|
||||
| --------------- | --------- | ----------- |
|
||||
| Daniel Jiang | [reasonerjt](https://github.com/reasonerjt) | [VMware](https://www.github.com/vmware/) |
|
||||
| Steven Ren | [renmaosheng](https://github.com/renmaosheng) | [VMware](https://www.github.com/vmware/) |
|
||||
| Steven Zou | [steven-zou](https://github.com/steven-zou) | [VMware](https://www.github.com/vmware/) |
|
||||
| Henry Zhang| [hainingzhang](https://github.com/hainingzhang)| [VMware](https://www.github.com/vmware/) |
|
||||
| Michael Michael |[michmike](https://github.com/michmike)| [VMware](https://www.github.com/vmware/) |
|
||||
|
||||
## Maintainers
|
||||
|
||||
| Maintainer | GitHub ID | Affiliation |
|
||||
| ---------- | --------- | ----------- |
|
||||
| Daojun Zhang | [stonezdj](https://github.com/stonezdj) | [VMware](https://www.github.com/vmware/) |
|
||||
| Wenkai Yin | [ywk253100](https://github.com/ywk253100) | [VMware](https://www.github.com/vmware/) |
|
||||
| Yan Wang | [wy65701436](https://github.com/wy65701436) | [VMware](https://www.github.com/vmware/) |
|
||||
| Qian Deng | [ninjadq](https://github.com/ninjadq) | [VMware](https://www.github.com/vmware/) |
|
||||
| Mia Zhou | [zhoumeina](https://github.com/zhoumeina) | [VMware](https://www.github.com/vmware/) |
|
||||
| Nathan Lowe | [nlowe](https://github.com/nlowe) | [Hyland Software](https://github.com/HylandSoftware) |
|
||||
| De Chen | [cd1989](https://github.com/cd1989) | [Caicloud](https://github.com/caicloud) |
|
||||
| Mingming Pei | [mmpei](https://github.com/mmpei) | [Netease](https://github.com/netease) |
|
||||
| Fanjian Kong | [kofj](https://github.com/kofj) | [Qihoo360](https://github.com/Qihoo360) |
|
||||
[GUIDING_PRINCIPLES.md](https://github.com/goharbor/community/blob/master/GUIDING_PRINCIPLES.md) contains the project vision, values and principles and how we apply them in making decisions.
|
||||
|
28
RELEASES.md
Normal file
28
RELEASES.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Versioning and Release
|
||||
This document describes the versioning and release process of Harbor. This document is a living document, contents will be updated according to each releases.
|
||||
|
||||
## Releases
|
||||
Harbor releases will be versioned using dotted triples, similar to [Semantic Version](http://semver.org/). For this specific document, we will refer to the respective components of this triple as `<major>.<minor>.<patch>`. The version number may have additional information, such as "-rc1,-rc2,-rc3" to mark release candidate builds for earlier access. Such releases will be considered as "pre-releases".
|
||||
|
||||
### Major and Minor Releases
|
||||
Major and minor releases of Harbor will be branched from master when the release reaches to `RC(release candidate)` state. The branch format should follow `release-<major>.<minor>.0`. For example, once the release `v1.0.0` reaches to RC, a branch will be created with the format `release-1.0.0`. When the release reaches to `GA(General Available)` state, The tag with format `v<major>.<minor>.<patch>` and should be made with command `git tag -s v<major>.<minor>.<patch>`. The release cadency is around 3 months, might be adjusted based on open source event, but will communicate it clearly.
|
||||
|
||||
### Patch releases
|
||||
Patch releases are based on the major/minor release branch, the release cadency for patch release of recent minor release is one month to solve critical community and security issues. The cadency for patch release of recent minus two minor releases are on-demand driven based on the severity of the issue to be fixed.
|
||||
|
||||
### Pre-releases
|
||||
`Pre-releases:mainly the different RC builds` will be compiled from their corresponding branches. Please note they are done to assist in the stabilization process, no guarantees are provided.
|
||||
|
||||
### Minor Release Support Matrix
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| Harbor v1.7.x | :white_check_mark: |
|
||||
| Harbor v1.8.x | :white_check_mark: |
|
||||
| Harbor v1.9.x | :white_check_mark: |
|
||||
|
||||
### Upgrade path and support policy
|
||||
The upgrade path for Harbor is (1) 1.0.x patch releases are always compatible with its major and minor version. For example, previous released 1.8.x can be upgraded to most recent 1.8.4 release. (2) Harbor only supports two previous minor releases to upgrade to current minor release. For example, 1.9.0 will only support 1.7.0 and 1.8.0 to upgrade from, 1.6.0 to 1.9.0 is not supported. One should upgrade to 1.8.0 first, then to 1.9.0.
|
||||
The Harbor project maintains release branches for the three most recent minor releases, each minor release will be maintained for approximately 9 months. There is no mandated timeline for major versions and there are currently no criteria for shipping a new major version (i.e. Harbor 2.0.0).
|
||||
|
||||
### Next Release
|
||||
The activity for next release will be tracked in the [up-to-date project board](https://github.com/orgs/goharbor/projects/1). If your issue is not present in the corresponding release, please reach out to the maintainers to add the issue to the project board.
|
14
SECURITY.md
14
SECURITY.md
@ -2,20 +2,8 @@
|
||||
Harbor is a large growing community devoted in creating a private enterprise-grade registry for all your cloud native assets. The community has adopted this security disclosure and response policy to ensure we responsibly handle critical issues.
|
||||
|
||||
## Supported Versions
|
||||
This section describes the maximum version skew supported between various Harbor releases. Harbor versions are expressed as **x.y.z**, where **x** is the major version, **y** is the minor version, and **z** is the patch version, following [Semantic Versioning terminology](https://semver.org/).
|
||||
The Harbor project maintains release branches for the three most recent minor releases. Applicable fixes, including security fixes, may be backported to those three release branches, depending on severity and feasibility. Please refer to [RELEASES.md](https://github.com/goharbor/harbor/blob/master/RELEASES.md) for details.
|
||||
|
||||
### Support Policy
|
||||
The Harbor project maintains release branches for the three most recent minor releases. Applicable fixes, including security fixes, may be backported to those three release branches, depending on severity and feasibility. Patch releases are cut from those branches at a regular cadence, or as needed. The Harbor project typically has a minor release approximately every 3 months, maintaining each minor release branch for approximately 9 months.
|
||||
|
||||
There is no mandated timeline for major versions and there are currently no criteria for shipping a new major version (i.e. Harbor 2.0.0).
|
||||
|
||||
### Minor Release Support Matrix
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| Harbor v1.7.x | :white_check_mark: |
|
||||
| Harbor v1.8.x | :white_check_mark: |
|
||||
| Harbor v1.9.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability - Private Disclosure Process
|
||||
Security is of the highest importance and all security vulnerabilities or suspected security vulnerabilities should be reported to Harbor privately, to minimize attacks against current users of Harbor before they are fixed. Vulnerabilities will be investigated and patched on the next patch (or minor) release as soon as possible. This information could be kept entirely internal to the project.
|
||||
|
||||
|
@ -43,25 +43,25 @@ You can compile the code by one of the three approaches:
|
||||
- Get official Golang image from docker hub:
|
||||
|
||||
```sh
|
||||
$ docker pull golang:1.12.5
|
||||
$ docker pull golang:1.12.12
|
||||
```
|
||||
|
||||
- Build, install and bring up Harbor without Notary:
|
||||
|
||||
```sh
|
||||
$ make install GOBUILDIMAGE=golang:1.12.5 COMPILETAG=compile_golangimage
|
||||
$ make install GOBUILDIMAGE=golang:1.12.12 COMPILETAG=compile_golangimage
|
||||
```
|
||||
|
||||
- Build, install and bring up Harbor with Notary:
|
||||
|
||||
```sh
|
||||
$ make install GOBUILDIMAGE=golang:1.12.5 COMPILETAG=compile_golangimage NOTARYFLAG=true
|
||||
$ make install GOBUILDIMAGE=golang:1.12.12 COMPILETAG=compile_golangimage NOTARYFLAG=true
|
||||
```
|
||||
|
||||
- Build, install and bring up Harbor with Clair:
|
||||
|
||||
```sh
|
||||
$ make install GOBUILDIMAGE=golang:1.12.5 COMPILETAG=compile_golangimage CLAIRFLAG=true
|
||||
$ make install GOBUILDIMAGE=golang:1.12.12 COMPILETAG=compile_golangimage CLAIRFLAG=true
|
||||
```
|
||||
|
||||
#### II. Compile code with your own Golang environment, then build Harbor
|
||||
|
BIN
docs/img/harbor-architecture-1.10.png
Normal file
BIN
docs/img/harbor-architecture-1.10.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 574 KiB |
@ -10,44 +10,45 @@ System admin have all permissions for the project.
|
||||
|
||||
The following table depicts the various user permission levels in a project.
|
||||
|
||||
| Action | Guest | Developer | Master | Project Admin |
|
||||
| --------------------------------------- | ----- | --------- | ------ | ------------- |
|
||||
| See the porject configurations | ✓ | ✓ | ✓ | ✓ |
|
||||
| Edit the project configurations | | | | ✓ |
|
||||
| See a list of project members | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create/edit/delete project members | | | | ✓ |
|
||||
| See a list of project logs | ✓ | ✓ | ✓ | ✓ |
|
||||
| See a list of project replications | | | ✓ | ✓ |
|
||||
| See a list of project replication jobs | | | | ✓ |
|
||||
| See a list of project labels | | | ✓ | ✓ |
|
||||
| Create/edit/delete project lables | | | ✓ | ✓ |
|
||||
| See a list of repositories | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create repositories | | ✓ | ✓ | ✓ |
|
||||
| Edit/delete repositories | | | ✓ | ✓ |
|
||||
| See a list of images | ✓ | ✓ | ✓ | ✓ |
|
||||
| Retag image | ✓ | ✓ | ✓ | ✓ |
|
||||
| Pull image | ✓ | ✓ | ✓ | ✓ |
|
||||
| Push image | | ✓ | ✓ | ✓ |
|
||||
| Scan/delete image | | | ✓ | ✓ |
|
||||
| See a list of image vulnerabilities | ✓ | ✓ | ✓ | ✓ |
|
||||
| See image build history | ✓ | ✓ | ✓ | ✓ |
|
||||
| Add/Remove labels of image | | ✓ | ✓ | ✓ |
|
||||
| See a list of helm charts | ✓ | ✓ | ✓ | ✓ |
|
||||
| Download helm charts | ✓ | ✓ | ✓ | ✓ |
|
||||
| Upload helm charts | | ✓ | ✓ | ✓ |
|
||||
| Delete helm charts | | | ✓ | ✓ |
|
||||
| See a list of helm chart versions | ✓ | ✓ | ✓ | ✓ |
|
||||
| Download helm chart versions | ✓ | ✓ | ✓ | ✓ |
|
||||
| Upload helm chart versions | | ✓ | ✓ | ✓ |
|
||||
| Delete helm chart versions | | | ✓ | ✓ |
|
||||
| Add/Remove labels of helm chart version | | ✓ | ✓ | ✓ |
|
||||
| See a list of project robots | | | ✓ | ✓ |
|
||||
| Create/edit/delete project robots | | | | ✓ |
|
||||
| See configured CVE whitelist | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create/edit/remove CVE whitelist | | | | ✓ |
|
||||
| Enable/disable webhooks | | ✓ | ✓ | ✓ |
|
||||
| Create/delete tag retention rules | | ✓ | ✓ | ✓ |
|
||||
| Enable/disable tag retention rules | | ✓ | ✓ | ✓ |
|
||||
| See project quotas | ✓ | ✓ | ✓ | ✓ |
|
||||
| Edit project quotas | | | | |
|
||||
| Action | Limited Guest | Guest | Developer | Master | Project Admin |
|
||||
| --------------------------------------- | ------------- | ----- | --------- | ------ | ------------- |
|
||||
| See the porject configurations | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Edit the project configurations | | | | | ✓ |
|
||||
| See a list of project members | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create/edit/delete project members | | | | | ✓ |
|
||||
| See a list of project logs | | ✓ | ✓ | ✓ | ✓ |
|
||||
| See a list of project replications | | | | ✓ | ✓ |
|
||||
| See a list of project replication jobs | | | | | ✓ |
|
||||
| See a list of project labels | | | | ✓ | ✓ |
|
||||
| Create/edit/delete project lables | | | | ✓ | ✓ |
|
||||
| See a list of repositories | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create repositories | | | ✓ | ✓ | ✓ |
|
||||
| Edit/delete repositories | | | | ✓ | ✓ |
|
||||
| See a list of images | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Retag image | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Pull image | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Push image | | | ✓ | ✓ | ✓ |
|
||||
| Scan/delete image | | | | ✓ | ✓ |
|
||||
| See a list of image vulnerabilities | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| See image build history | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Add/Remove labels of image | | | ✓ | ✓ | ✓ |
|
||||
| See a list of helm charts | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Download helm charts | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Upload helm charts | | | ✓ | ✓ | ✓ |
|
||||
| Delete helm charts | | | | ✓ | ✓ |
|
||||
| See a list of helm chart versions | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Download helm chart versions | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Upload helm chart versions | | | ✓ | ✓ | ✓ |
|
||||
| Delete helm chart versions | | | | ✓ | ✓ |
|
||||
| Add/Remove labels of helm chart version | | | ✓ | ✓ | ✓ |
|
||||
| See a list of project robots | | | | ✓ | ✓ |
|
||||
| Create/edit/delete project robots | | | | | ✓ |
|
||||
| See configured CVE whitelist | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create/edit/remove CVE whitelist | | | | | ✓ |
|
||||
| Enable/disable webhooks | | | ✓ | ✓ | ✓ |
|
||||
| Create/delete tag retention rules | | | ✓ | ✓ | ✓ |
|
||||
| Enable/disable tag retention rules | | | ✓ | ✓ | ✓ |
|
||||
| See project quotas | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Edit project quotas | | | | | |
|
||||
|
||||
|
||||
|
@ -1,18 +1,32 @@
|
||||
# Registry Landscape
|
||||
The cloud native ecosystem is moving rapidly–registries and their featuresets are no exception. We've made our best effort to survey the container registry landscape and compare to our core featureset.
|
||||
The cloud native ecosystem is moving rapidly–registries and their feature sets are no exception. We've made our best effort to survey the container registry landscape and compare to our core feature set.
|
||||
|
||||
If you find something outdated or outright erroneous, please submit a PR and we'll fix it right away.
|
||||
|
||||
| Feature | Harbor | Docker Trusted Registry | Quay | Cloud Providers (GCP, AWS, Azure) | Docker Distribution | Artifactory |
|
||||
| -------------: | :----: | :---------------------: | :--: | :-------------------------------: | :-----------------: | :---------: |
|
||||
| Local Auth | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ |
|
||||
| LDAP-based Auth | ✓ | ✓ | ✓ | partial | ✗ | ✓ |
|
||||
| Content Trust and Validation | ✓ | ✓ | ✗ | ✗ | partial | partial |
|
||||
| Vulnerability Scanning & Monitoring | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ |
|
||||
| Replication | ✓ | ✓ | ✓ | n/a | ✗ | ✓ |
|
||||
| Multi-Tenancy (projects, teams, etc.) | ✓ | ✓ | ✓ | partial | ✗ | ✓ |
|
||||
| Role-Based Access Control | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ |
|
||||
| Custom TLS Certificates | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ |
|
||||
| Ability to Determine Version of Binaries in Containers | ✓ | ✓ | ✓ | ✗ | ✗ | ? |
|
||||
| Upstream Registry Proxy Cache | ✗ | ✓ | ✗ | ✗ | ✓ | ✓ |
|
||||
| Audit Logs | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ |
|
||||
Table updated on 10/21/2019 against Harbor 1.9.
|
||||
|
||||
| Feature | Harbor | Docker Trusted Registry | Quay | Cloud Providers (GCP, AWS, Azure) | Docker Distribution | Artifactory | GitLab |
|
||||
| -------------: | :----: | :---------------------: | :-----: | :-------------------------------: | :-----------------: | :---------: | :------: |
|
||||
| Ability to Determine Version of Binaries in Containers | ✓ | ✓ | ✓ | ✗ | ✗ | ? | ? |
|
||||
| Artifact Repository (rpms, git, jar, etc) | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | partial |
|
||||
| Audit Logs | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ |
|
||||
| Content Trust and Validation | ✓ | ✓ | ✗ | ✗ | partial | partial | ✗ |
|
||||
| Custom TLS Certificates | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ |
|
||||
| Helm Chart Repository Manager | ✓ | ✗ | partial | ✗ | ✗ | ✓ | ✗ |
|
||||
| LDAP-based Auth | ✓ | ✓ | ✓ | partial | ✗ | ✓ | ✓ |
|
||||
| Local Auth | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ |
|
||||
| Multi-Tenancy (projects, teams, namespaces, etc) | ✓ | ✓ | ✓ | partial | ✗ | ✓ | ✓ |
|
||||
| Open Source | ✓ | partial | ✗ | ✗ | ✓ | partial | partial |
|
||||
| Project Quotas (by image count & storage consumption) | ✓ | ✗ | ✗ | partial | ✗ | ✗ | ✗ |
|
||||
| Replication between instances | ✓ | ✓ | ✓ | n/a | ✗ | ✓ | ✗ |
|
||||
| Replication between non-instances | ✓ | ✗ | ✓ | n/a | ✗ | ✗ | ✗ |
|
||||
| Robot Accounts for Helm Charts | ✓ | ✗ | ✗ | ? | ✗ | ✗ | ✗ |
|
||||
| Robot Accounts for Images | ✓ | ? | ✓ | ? | ✗ | ? | ? |
|
||||
| Role-Based Access Control | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✗ |
|
||||
| Single Sign On (OIDC) | ✓ | ✓ | ✓ | ✓ | ✗ | partial | ✗ |
|
||||
| Tag Retention Policy | ✓ | ✗ | partial | ✗ | ✗ | ✗ | ✗ |
|
||||
| Upstream Registry Proxy Cache | ✗ | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ |
|
||||
| Vulnerability Scanning & Monitoring | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | partial |
|
||||
| Vulnerability Scanning Plugin Framework | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Vulnerability Whitelisting | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Webhooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
|
@ -1089,6 +1089,8 @@ paths:
|
||||
description: Forbidden.
|
||||
'404':
|
||||
description: Repository not found.
|
||||
'412':
|
||||
description: Precondition Failed.
|
||||
put:
|
||||
summary: Update description of the repository.
|
||||
description: |
|
||||
|
@ -36,10 +36,10 @@ version | set harbor version
|
||||
#### EXAMPLE:
|
||||
|
||||
#### Build and run harbor from source code.
|
||||
make install GOBUILDIMAGE=golang:1.12.5 COMPILETAG=compile_golangimage NOTARYFLAG=true
|
||||
make install GOBUILDIMAGE=golang:1.12.12 COMPILETAG=compile_golangimage NOTARYFLAG=true
|
||||
|
||||
### Package offline installer
|
||||
make package_offline GOBUILDIMAGE=golang:1.12.5 COMPILETAG=compile_golangimage NOTARYFLAG=true
|
||||
make package_offline GOBUILDIMAGE=golang:1.12.12 COMPILETAG=compile_golangimage NOTARYFLAG=true
|
||||
|
||||
### Start harbor with notary
|
||||
make -e NOTARYFLAG=true start
|
||||
|
@ -1,11 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set +o noglob
|
||||
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source $DIR/common.sh
|
||||
|
||||
set +o noglob
|
||||
|
||||
usage=$'Please set hostname and other necessary attributes in harbor.yml first. DO NOT use localhost or 127.0.0.1 for hostname, because Harbor needs to be accessed by external clients.
|
||||
Please set --with-notary if needs enable Notary in Harbor, and set ui_url_protocol/ssl_cert/ssl_cert_key in harbor.yml bacause notary must run under https.
|
||||
Please set --with-clair if needs enable Clair in Harbor
|
||||
|
@ -57,4 +57,7 @@ DROP TABLE IF EXISTS img_scan_job;
|
||||
DROP TRIGGER IF EXISTS TRIGGER ON img_scan_overview;
|
||||
DROP TABLE IF EXISTS img_scan_overview;
|
||||
|
||||
DROP TABLE IF EXISTS clair_vuln_timestamp
|
||||
DROP TABLE IF EXISTS clair_vuln_timestamp;
|
||||
|
||||
/* Add limited guest role */
|
||||
INSERT INTO role (role_code, name) VALUES ('LRS', 'limitedGuest');
|
||||
|
@ -97,7 +97,7 @@ DOCKERIMAGENAME_MIGRATOR=goharbor/harbor-migrator
|
||||
# for chart server (chartmuseum)
|
||||
DOCKERFILEPATH_CHART_SERVER=$(DOCKERFILEPATH)/chartserver
|
||||
DOCKERFILENAME_CHART_SERVER=Dockerfile
|
||||
CHART_SERVER_CODE_BASE=github.com/helm/chartmuseum
|
||||
CHART_SERVER_CODE_BASE=https://github.com/helm/chartmuseum.git
|
||||
CHART_SERVER_MAIN_PATH=cmd/chartmuseum
|
||||
CHART_SERVER_BIN_NAME=chartm
|
||||
|
||||
@ -195,7 +195,7 @@ _build_registry:
|
||||
rm -rf $(DOCKERFILEPATH_REG)/binary && mkdir -p $(DOCKERFILEPATH_REG)/binary && \
|
||||
$(call _get_binary, https://storage.googleapis.com/harbor-builds/bin/registry/release-$(REGISTRYVERSION)/registry, $(DOCKERFILEPATH_REG)/binary/registry); \
|
||||
else \
|
||||
cd $(DOCKERFILEPATH_REG) && $(DOCKERFILEPATH_REG)/builder $(REGISTRYVERSION) && cd - ; \
|
||||
cd $(DOCKERFILEPATH_REG) && $(DOCKERFILEPATH_REG)/builder $(REGISTRY_SRC_TAG) && cd - ; \
|
||||
fi
|
||||
@echo "building registry container for photon..."
|
||||
@chmod 655 $(DOCKERFILEPATH_REG)/binary/registry && $(DOCKERBUILD) -f $(DOCKERFILEPATH_REG)/$(DOCKERFILENAME_REG) -t $(DOCKERIMAGENAME_REG):$(REGISTRYVERSION)-$(VERSIONTAG) .
|
||||
|
@ -30,4 +30,4 @@ cp compile.sh binary/
|
||||
docker run -it --rm -v $cur/binary:/go/bin --name golang_code_builder $GOLANG_IMAGE /bin/bash /go/bin/compile.sh $GIT_PATH $CODE_VERSION $MAIN_GO_PATH $BIN_NAME
|
||||
|
||||
#Clear
|
||||
docker rm -f golang_code_builder
|
||||
#docker rm -f golang_code_builder
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM golang:1.12.5
|
||||
FROM golang:1.12.12
|
||||
|
||||
ADD . /go/src/github.com/goharbor/harbor-scanner-clair/
|
||||
WORKDIR /go/src/github.com/goharbor/harbor-scanner-clair/
|
||||
|
@ -23,7 +23,7 @@ TEMP=`mktemp -d ${TMPDIR-/tmp}/clair-adapter.XXXXXX`
|
||||
git clone https://github.com/danielpacak/harbor-scanner-clair.git $TEMP
|
||||
cd $TEMP; git checkout $VERSION; cd -
|
||||
|
||||
echo 'build the clair adapter binary bases on the golang:1.12.5...'
|
||||
echo 'build the clair adapter binary bases on the golang:1.12.12'
|
||||
cp Dockerfile.binary $TEMP
|
||||
docker build -f $TEMP/Dockerfile.binary -t clair-adapter-golang $TEMP
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM golang:1.11.2
|
||||
FROM golang:1.12.12
|
||||
|
||||
ADD . /go/src/github.com/coreos/clair/
|
||||
WORKDIR /go/src/github.com/coreos/clair/
|
||||
|
@ -23,7 +23,7 @@ TEMP=`mktemp -d /$TMPDIR/clair.XXXXXX`
|
||||
git clone https://github.com/coreos/clair.git $TEMP
|
||||
cd $TEMP; git checkout $VERSION; cd -
|
||||
|
||||
echo 'build the clair binary bases on the golang:1.11.2...'
|
||||
echo 'build the clair binary bases on the golang:1.12.12'
|
||||
cp Dockerfile.binary $TEMP
|
||||
docker build -f $TEMP/Dockerfile.binary -t clair-golang $TEMP
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM golang:1.11.2
|
||||
FROM golang:1.12.12
|
||||
|
||||
ARG NOTARY_VERSION
|
||||
ARG MIGRATE_VERSION
|
||||
|
@ -24,6 +24,8 @@ docker cp $ID:/go/bin/notary-signer binary/
|
||||
docker cp $ID:/go/bin/migrate binary/
|
||||
docker cp $ID:/migrations binary/
|
||||
|
||||
sed -i 's/waiting for $DB_URL/waiting for database/g' binary/migrations/migrate.sh
|
||||
|
||||
docker rm -f $ID
|
||||
docker rmi -f notary-binary
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM golang:1.11
|
||||
FROM golang:1.12.12
|
||||
|
||||
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
|
||||
ENV BUILDTAGS include_oss include_gcs
|
||||
|
@ -29,7 +29,7 @@ wget https://github.com/docker/distribution/pull/2879.patch
|
||||
git apply 2879.patch
|
||||
cd $cur
|
||||
|
||||
echo 'build the registry binary bases on the golang:1.11...'
|
||||
echo 'build the registry binary ...'
|
||||
cp Dockerfile.binary $TEMP
|
||||
docker build -f $TEMP/Dockerfile.binary -t registry-golang $TEMP
|
||||
|
||||
|
@ -18,6 +18,7 @@ package metadata
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -139,12 +140,12 @@ type Int64Type struct {
|
||||
}
|
||||
|
||||
func (t *Int64Type) validate(str string) error {
|
||||
_, err := strconv.ParseInt(str, 10, 64)
|
||||
_, err := parseInt64(str)
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Int64Type) get(str string) (interface{}, error) {
|
||||
return strconv.ParseInt(str, 10, 64)
|
||||
return parseInt64(str)
|
||||
}
|
||||
|
||||
// BoolType ...
|
||||
@ -194,7 +195,7 @@ type QuotaType struct {
|
||||
}
|
||||
|
||||
func (t *QuotaType) validate(str string) error {
|
||||
val, err := strconv.ParseInt(str, 10, 64)
|
||||
val, err := parseInt64(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -205,3 +206,18 @@ func (t *QuotaType) validate(str string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseInt64 returns int64 from string which support scientific notation
|
||||
func parseInt64(str string) (int64, error) {
|
||||
val, err := strconv.ParseInt(str, 10, 64)
|
||||
if err == nil {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
fval, err := strconv.ParseFloat(str, 64)
|
||||
if err == nil && fval == math.Trunc(fval) {
|
||||
return int64(fval), nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("invalid int64 string: %s", str)
|
||||
}
|
||||
|
@ -15,8 +15,9 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIntType_validate(t *testing.T) {
|
||||
@ -96,3 +97,33 @@ func TestMapType_get(t *testing.T) {
|
||||
result, _ := test.get(`{"sample":"abc", "another":"welcome"}`)
|
||||
assert.Equal(t, map[string]interface{}{"sample": "abc", "another": "welcome"}, result)
|
||||
}
|
||||
|
||||
func Test_parseInt64(t *testing.T) {
|
||||
type args struct {
|
||||
str string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int64
|
||||
wantErr bool
|
||||
}{
|
||||
{"1", args{"1"}, int64(1), false},
|
||||
{"1.0", args{"1.0"}, int64(1), false},
|
||||
{"1.1", args{"1.1"}, int64(0), true},
|
||||
{"1E2", args{"1E2"}, int64(100), false},
|
||||
{"1.073741824e+11", args{"1.073741824e+11"}, int64(107374182400), false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseInt64(tt.args.str)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseInt64() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("parseInt64() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ const (
|
||||
RoleDeveloper = 2
|
||||
RoleGuest = 3
|
||||
RoleMaster = 4
|
||||
RoleLimitedGuest = 5
|
||||
|
||||
LabelLevelSystem = "s"
|
||||
LabelLevelUser = "u"
|
||||
|
@ -78,7 +78,7 @@ func addProjectMember(member models.Member) (int, error) {
|
||||
func GetProjectByID(id int64) (*models.Project, error) {
|
||||
o := GetOrmer()
|
||||
|
||||
sql := `select p.project_id, p.name, u.username as owner_name, p.owner_id, p.creation_time, p.update_time
|
||||
sql := `select p.project_id, p.name, u.username as owner_name, p.owner_id, p.creation_time, p.update_time
|
||||
from project p left join harbor_user u on p.owner_id = u.user_id where p.deleted = false and p.project_id = ?`
|
||||
queryParam := make([]interface{}, 1)
|
||||
queryParam = append(queryParam, id)
|
||||
@ -142,7 +142,7 @@ func GetTotalOfProjects(query *models.ProjectQueryParam) (int64, error) {
|
||||
// GetProjects returns a project list according to the query conditions
|
||||
func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) {
|
||||
sqlStr, queryParam := projectQueryConditions(query)
|
||||
sqlStr = `select distinct p.project_id, p.name, p.owner_id,
|
||||
sqlStr = `select distinct p.project_id, p.name, p.owner_id,
|
||||
p.creation_time, p.update_time ` + sqlStr + ` order by p.name`
|
||||
sqlStr, queryParam = CreatePagination(query, sqlStr, queryParam)
|
||||
|
||||
@ -158,15 +158,15 @@ func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) {
|
||||
// and the user is in the group which is a group member of this project.
|
||||
func GetGroupProjects(groupIDs []int, query *models.ProjectQueryParam) ([]*models.Project, error) {
|
||||
sql, params := projectQueryConditions(query)
|
||||
sql = `select distinct p.project_id, p.name, p.owner_id,
|
||||
sql = `select distinct p.project_id, p.name, p.owner_id,
|
||||
p.creation_time, p.update_time ` + sql
|
||||
groupIDCondition := JoinNumberConditions(groupIDs)
|
||||
if len(groupIDs) > 0 {
|
||||
sql = fmt.Sprintf(
|
||||
`%s union select distinct p.project_id, p.name, p.owner_id, p.creation_time, p.update_time
|
||||
from project p
|
||||
`%s union select distinct p.project_id, p.name, p.owner_id, p.creation_time, p.update_time
|
||||
from project p
|
||||
left join project_member pm on p.project_id = pm.project_id
|
||||
left join user_group ug on ug.id = pm.entity_id and pm.entity_type = 'g'
|
||||
left join user_group ug on ug.id = pm.entity_id and pm.entity_type = 'g'
|
||||
where ug.id in ( %s )`,
|
||||
sql, groupIDCondition)
|
||||
}
|
||||
@ -188,11 +188,11 @@ func GetTotalGroupProjects(groupIDs []int, query *models.ProjectQueryParam) (int
|
||||
sql = `select count(1) ` + sqlCondition
|
||||
} else {
|
||||
sql = fmt.Sprintf(
|
||||
`select count(1)
|
||||
from ( select p.project_id %s union select p.project_id
|
||||
from project p
|
||||
`select count(1)
|
||||
from ( select p.project_id %s union select p.project_id
|
||||
from project p
|
||||
left join project_member pm on p.project_id = pm.project_id
|
||||
left join user_group ug on ug.id = pm.entity_id and pm.entity_type = 'g'
|
||||
left join user_group ug on ug.id = pm.entity_id and pm.entity_type = 'g'
|
||||
where ug.id in ( %s )) t`,
|
||||
sqlCondition, groupIDCondition)
|
||||
}
|
||||
@ -254,6 +254,8 @@ func projectQueryConditions(query *models.ProjectQueryParam) (string, []interfac
|
||||
roleID = 3
|
||||
case common.RoleMaster:
|
||||
roleID = 4
|
||||
case common.RoleLimitedGuest:
|
||||
roleID = 5
|
||||
}
|
||||
params = append(params, roleID)
|
||||
}
|
||||
@ -287,8 +289,8 @@ func DeleteProject(id int64) error {
|
||||
return err
|
||||
}
|
||||
name := fmt.Sprintf("%s#%d", project.Name, project.ProjectID)
|
||||
sql := `update project
|
||||
set deleted = true, name = ?
|
||||
sql := `update project
|
||||
set deleted = true, name = ?
|
||||
where project_id = ?`
|
||||
_, err = GetOrmer().Raw(sql, name, id).Exec()
|
||||
return err
|
||||
@ -304,8 +306,8 @@ func GetRolesByGroupID(projectID int64, groupIDs []int) ([]int, error) {
|
||||
groupIDCondition := JoinNumberConditions(groupIDs)
|
||||
o := GetOrmer()
|
||||
sql := fmt.Sprintf(
|
||||
`select distinct pm.role from project_member pm
|
||||
left join user_group ug on pm.entity_type = 'g' and pm.entity_id = ug.id
|
||||
`select distinct pm.role from project_member pm
|
||||
left join user_group ug on pm.entity_type = 'g' and pm.entity_id = ug.id
|
||||
where ug.id in ( %s ) and pm.project_id = ?`,
|
||||
groupIDCondition)
|
||||
log.Debugf("sql for GetRolesByGroupID(project ID: %d, group ids: %v):%v", projectID, groupIDs, sql)
|
||||
|
@ -201,6 +201,7 @@ type ProjectSummary struct {
|
||||
MasterCount int64 `json:"master_count"`
|
||||
DeveloperCount int64 `json:"developer_count"`
|
||||
GuestCount int64 `json:"guest_count"`
|
||||
LimitedGuestCount int64 `json:"limited_guest_count"`
|
||||
|
||||
Quota struct {
|
||||
Hard types.ResourceList `json:"hard"`
|
||||
|
@ -73,6 +73,7 @@ type TagDetail struct {
|
||||
Author string `json:"author"`
|
||||
Created time.Time `json:"created"`
|
||||
Config *TagCfg `json:"config"`
|
||||
Immutable bool `json:"immutable"`
|
||||
}
|
||||
|
||||
// TagCfg ...
|
||||
|
@ -43,6 +43,7 @@ const (
|
||||
ResourceLog = Resource("log")
|
||||
ResourceMember = Resource("member")
|
||||
ResourceMetadata = Resource("metadata")
|
||||
ResourceQuota = Resource("quota")
|
||||
ResourceReplication = Resource("replication") // TODO remove
|
||||
ResourceReplicationJob = Resource("replication-job") // TODO remove
|
||||
ResourceReplicationExecution = Resource("replication-execution")
|
||||
|
@ -48,124 +48,7 @@ var (
|
||||
}
|
||||
|
||||
// all policies for the projects
|
||||
allPolicies = []*rbac.Policy{
|
||||
{Resource: rbac.ResourceSelf, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceSelf, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceSelf, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceMember, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceMember, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceMember, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceMember, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceMember, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceMetadata, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceMetadata, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceMetadata, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceMetadata, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceLog, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceReplicationJob, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceReplicationJob, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceReplicationJob, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceReplicationExecution, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceReplicationExecution, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceReplicationExecution, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceReplicationExecution, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceReplicationExecution, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceReplicationTask, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceReplicationTask, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceReplicationTask, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceReplicationTask, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceReplicationTask, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionOperate},
|
||||
|
||||
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceLabelResource, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionPull},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionPush},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTag, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRepositoryTag, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceRepositoryTag, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTagScanJob, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRepositoryTagScanJob, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTagVulnerability, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTagManifest, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTagLabel, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRepositoryTagLabel, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceRepositoryTagLabel, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceHelmChart, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceHelmChart, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceHelmChart, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceHelmChart, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceConfiguration, Action: rbac.ActionUpdate},
|
||||
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceNotificationPolicy, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceNotificationPolicy, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceNotificationPolicy, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceNotificationPolicy, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceNotificationPolicy, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceScan, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceScan, Action: rbac.ActionRead},
|
||||
}
|
||||
allPolicies = computeAllPolicies()
|
||||
)
|
||||
|
||||
// PoliciesForPublicProject ...
|
||||
@ -197,3 +80,19 @@ func GetAllPolicies(namespace rbac.Namespace) []*rbac.Policy {
|
||||
|
||||
return policies
|
||||
}
|
||||
|
||||
func computeAllPolicies() []*rbac.Policy {
|
||||
var results []*rbac.Policy
|
||||
|
||||
mp := map[string]bool{}
|
||||
for _, policies := range rolePoliciesMap {
|
||||
for _, policy := range policies {
|
||||
if !mp[policy.String()] {
|
||||
results = append(results, policy)
|
||||
mp[policy.String()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
@ -53,6 +53,8 @@ var (
|
||||
|
||||
{Resource: rbac.ResourceLabelResource, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceQuota, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionUpdate},
|
||||
@ -137,6 +139,8 @@ var (
|
||||
|
||||
{Resource: rbac.ResourceLog, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceQuota, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionList},
|
||||
|
||||
@ -220,6 +224,8 @@ var (
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceQuota, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionUpdate},
|
||||
@ -273,6 +279,8 @@ var (
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceQuota, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionPull},
|
||||
@ -299,6 +307,30 @@ var (
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionList},
|
||||
},
|
||||
|
||||
"limitedGuest": {
|
||||
{Resource: rbac.ResourceSelf, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceQuota, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionPull},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTag, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRepositoryTag, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTagVulnerability, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTagManifest, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceHelmChart, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceHelmChart, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@ -319,6 +351,8 @@ func (role *visitorRole) GetRoleName() string {
|
||||
return "developer"
|
||||
case common.RoleGuest:
|
||||
return "guest"
|
||||
case common.RoleLimitedGuest:
|
||||
return "limitedGuest"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ func (suite *VisitorRoleTestSuite) TestGetRoleName() {
|
||||
guest := visitorRole{roleID: common.RoleGuest}
|
||||
suite.Equal(guest.GetRoleName(), "guest")
|
||||
|
||||
limitedGuest := visitorRole{roleID: common.RoleLimitedGuest}
|
||||
suite.Equal(limitedGuest.GetRoleName(), "limitedGuest")
|
||||
|
||||
unknow := visitorRole{roleID: 404}
|
||||
suite.Equal(unknow.GetRoleName(), "")
|
||||
}
|
||||
|
@ -125,6 +125,8 @@ func (s *SecurityContext) GetProjectRoles(projectIDOrName interface{}) []int {
|
||||
roles = append(roles, common.RoleDeveloper)
|
||||
case "RS":
|
||||
roles = append(roles, common.RoleGuest)
|
||||
case "LRS":
|
||||
roles = append(roles, common.RoleLimitedGuest)
|
||||
}
|
||||
}
|
||||
return mergeRoles(roles, s.GetRolesByGroup(projectIDOrName))
|
||||
|
@ -221,26 +221,6 @@ func (session *Session) SearchUser(username string) ([]models.LdapUser, error) {
|
||||
u.GroupDNList = groupDNList
|
||||
}
|
||||
|
||||
log.Debugf("Searching for nested groups")
|
||||
nestedGroupDNList := []string{}
|
||||
nestedGroupFilter := createNestedGroupFilter(ldapEntry.DN)
|
||||
result, err := session.SearchLdap(nestedGroupFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, groupEntry := range result.Entries {
|
||||
if !contains(u.GroupDNList, groupEntry.DN) {
|
||||
nestedGroupDNList = append(nestedGroupDNList, strings.TrimSpace(groupEntry.DN))
|
||||
log.Debugf("Found group %v", groupEntry.DN)
|
||||
} else {
|
||||
log.Debugf("%v is already in GroupDNList", groupEntry.DN)
|
||||
}
|
||||
}
|
||||
|
||||
u.GroupDNList = append(u.GroupDNList, nestedGroupDNList...)
|
||||
log.Debugf("Done searching for nested groups")
|
||||
|
||||
u.DN = ldapEntry.DN
|
||||
ldapUsers = append(ldapUsers, u)
|
||||
|
||||
@ -442,12 +422,6 @@ func createGroupSearchFilter(oldFilter, groupName, groupNameAttribute string) st
|
||||
return filter
|
||||
}
|
||||
|
||||
func createNestedGroupFilter(userDN string) string {
|
||||
filter := ""
|
||||
filter = "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:=" + goldap.EscapeFilter(userDN) + "))"
|
||||
return filter
|
||||
}
|
||||
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
|
@ -106,7 +106,7 @@ var insecureTransport = &http.Transport{
|
||||
// Token wraps the attributes of a oauth2 token plus the attribute of ID token
|
||||
type Token struct {
|
||||
oauth2.Token
|
||||
IDToken string `json:"id_token"`
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
}
|
||||
|
||||
func getOauthConf() (*oauth2.Config, error) {
|
||||
@ -200,9 +200,10 @@ func RefreshToken(ctx context.Context, token *Token) (*Token, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
it, ok := t.Extra("id_token").(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get id_token from refresh response")
|
||||
log.Debugf("id_token not exist in refresh response")
|
||||
}
|
||||
return &Token{Token: *t, IDToken: it}, nil
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/pkg/errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SecretVerifyError wraps the different errors happened when verifying a secret for OIDC user. When seeing this error,
|
||||
@ -83,7 +84,10 @@ func (dm *defaultManager) VerifySecret(ctx context.Context, userID int, secret s
|
||||
return dm.VerifyToken(ctx, oidcUser)
|
||||
}
|
||||
|
||||
// VerifyToken verifies the token in the model from parm in this implementation it will try to refresh the token
|
||||
// VerifyToken checks the expiration of the token in the model, (we'll only do expiration checks b/c according to spec,
|
||||
// the response may not have ID token:
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse
|
||||
// and it will try to refresh the token
|
||||
// if it's expired, if the refresh is successful it will persist the token and consider the verification successful.
|
||||
func (dm *defaultManager) VerifyToken(ctx context.Context, user *models.OIDCUser) error {
|
||||
if user == nil {
|
||||
@ -103,11 +107,10 @@ func (dm *defaultManager) VerifyToken(ctx context.Context, user *models.OIDCUser
|
||||
return verifyError(err)
|
||||
}
|
||||
log.Debugf("Token string for verify: %s", tokenStr)
|
||||
_, err = VerifyToken(ctx, token.IDToken)
|
||||
if err == nil {
|
||||
if token.Expiry.After(time.Now()) {
|
||||
return nil
|
||||
}
|
||||
log.Infof("Failed to verify ID Token, error: %v, refreshing...", err)
|
||||
log.Info("Token string has expired, refreshing...")
|
||||
t, err := RefreshToken(ctx, token)
|
||||
if err != nil {
|
||||
return verifyError(err)
|
||||
|
@ -593,8 +593,18 @@ func (p *ProjectAPI) Summary() {
|
||||
ChartCount: p.project.ChartCount,
|
||||
}
|
||||
|
||||
var fetchSummaries []func(int64, *models.ProjectSummary)
|
||||
|
||||
if hasPerm, _ := p.HasProjectPermission(p.project.ProjectID, rbac.ActionRead, rbac.ResourceQuota); hasPerm {
|
||||
fetchSummaries = append(fetchSummaries, getProjectQuotaSummary)
|
||||
}
|
||||
|
||||
if hasPerm, _ := p.HasProjectPermission(p.project.ProjectID, rbac.ActionList, rbac.ResourceMember); hasPerm {
|
||||
fetchSummaries = append(fetchSummaries, getProjectMemberSummary)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, fn := range []func(int64, *models.ProjectSummary){getProjectQuotaSummary, getProjectMemberSummary} {
|
||||
for _, fn := range fetchSummaries {
|
||||
fn := fn
|
||||
|
||||
wg.Add(1)
|
||||
|
@ -191,7 +191,7 @@ func (pma *ProjectMemberAPI) Put() {
|
||||
pma.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
if req.Role < 1 || req.Role > 4 {
|
||||
if !isValidRole(req.Role) {
|
||||
pma.SendBadRequestError(fmt.Errorf("Invalid role id %v", req.Role))
|
||||
return
|
||||
}
|
||||
@ -284,9 +284,22 @@ func AddProjectMember(projectID int64, request models.MemberReq) (int, error) {
|
||||
return 0, ErrDuplicateProjectMember
|
||||
}
|
||||
|
||||
if member.Role < 1 || member.Role > 4 {
|
||||
if !isValidRole(member.Role) {
|
||||
// Return invalid role error
|
||||
return 0, ErrInvalidRole
|
||||
}
|
||||
return project.AddProjectMember(member)
|
||||
}
|
||||
|
||||
func isValidRole(role int) bool {
|
||||
switch role {
|
||||
case common.RoleProjectAdmin,
|
||||
common.RoleMaster,
|
||||
common.RoleDeveloper,
|
||||
common.RoleGuest,
|
||||
common.RoleLimitedGuest:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -344,3 +344,28 @@ func TestProjectMemberAPI_PutAndDelete(t *testing.T) {
|
||||
runCodeCheckingCases(t, cases...)
|
||||
|
||||
}
|
||||
|
||||
func Test_isValidRole(t *testing.T) {
|
||||
type args struct {
|
||||
role int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{"project admin", args{1}, true},
|
||||
{"master", args{4}, true},
|
||||
{"developer", args{2}, true},
|
||||
{"guest", args{3}, true},
|
||||
{"limited guest", args{5}, true},
|
||||
{"unknow", args{6}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isValidRole(tt.args.role); got != tt.want {
|
||||
t.Errorf("isValidRole() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,8 @@ import (
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
notifierEvt "github.com/goharbor/harbor/src/core/notifier/event"
|
||||
coreutils "github.com/goharbor/harbor/src/core/utils"
|
||||
"github.com/goharbor/harbor/src/pkg/art"
|
||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||
"github.com/goharbor/harbor/src/replication"
|
||||
"github.com/goharbor/harbor/src/replication/event"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
@ -729,6 +731,9 @@ func assembleTag(c chan *models.TagResp, client *registry.Repository, projectID
|
||||
}
|
||||
}
|
||||
|
||||
// get immutable status
|
||||
item.Immutable = isImmutable(projectID, repository, tag)
|
||||
|
||||
c <- item
|
||||
}
|
||||
|
||||
@ -809,6 +814,21 @@ func populateAuthor(detail *models.TagDetail) {
|
||||
}
|
||||
}
|
||||
|
||||
// check whether the tag is immutable
|
||||
func isImmutable(projectID int64, repo string, tag string) bool {
|
||||
_, repoName := utils.ParseRepository(repo)
|
||||
matched, err := rule.NewRuleMatcher(projectID).Match(art.Candidate{
|
||||
Repository: repoName,
|
||||
Tag: tag,
|
||||
NamespaceID: projectID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return false
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
// GetManifests returns the manifest of a tag
|
||||
func (ra *RepositoryAPI) GetManifests() {
|
||||
repoName := ra.GetString(":splat")
|
||||
@ -954,7 +974,7 @@ func (ra *RepositoryAPI) Put() {
|
||||
}
|
||||
|
||||
if !ra.SecurityCtx.IsAuthenticated() {
|
||||
ra.SendUnAuthorizedError(errors.New("Unauthorized"))
|
||||
ra.SendUnAuthorizedError(errors.New("unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -115,6 +115,7 @@ func TestSysCVEWhitelistAPIPut(t *testing.T) {
|
||||
ExpiresAt: &s,
|
||||
Items: []models.CVEWhitelistItem{
|
||||
{CVEID: "CVE-2019-12310"},
|
||||
{CVEID: "RHSA-2019:2237"},
|
||||
},
|
||||
},
|
||||
credential: sysAdmin,
|
||||
|
@ -455,26 +455,12 @@ func (s *sessionReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||
log.Info("can not get user information from session")
|
||||
return false
|
||||
}
|
||||
if ctx.Request.Context().Value(AuthModeKey).(string) == common.OIDCAuth {
|
||||
ou, err := dao.GetOIDCUserByUserID(user.UserID)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get OIDC user info, error: %v", err)
|
||||
return false
|
||||
}
|
||||
if ou != nil { // If user does not have OIDC metadata, it means he is not onboarded via OIDC authn,
|
||||
// so we can skip checking the token.
|
||||
if err := oidc.VerifyAndPersistToken(ctx.Request.Context(), ou); err != nil {
|
||||
log.Errorf("Failed to verify token, error: %v", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debug("using local database project manager")
|
||||
pm := config.GlobalProjectMgr
|
||||
log.Debug("creating local database security context...")
|
||||
securCtx := local.NewSecurityContext(&user, pm)
|
||||
securityCtx := local.NewSecurityContext(&user, pm)
|
||||
|
||||
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
||||
setSecurCtxAndPM(ctx.Request, securityCtx, pm)
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -35,4 +35,4 @@ var ChartMiddlewares = []string{CHART}
|
||||
var Middlewares = []string{READONLY, URL, MUITIPLEMANIFEST, LISTREPO, CONTENTTRUST, VULNERABLE, SIZEQUOTA, IMMUTABLE, COUNTQUOTA}
|
||||
|
||||
// MiddlewaresLocal ...
|
||||
var MiddlewaresLocal = []string{SIZEQUOTA, COUNTQUOTA}
|
||||
var MiddlewaresLocal = []string{SIZEQUOTA, IMMUTABLE, COUNTQUOTA}
|
||||
|
54
src/core/middlewares/immutable/builder.go
Normal file
54
src/core/middlewares/immutable/builder.go
Normal file
@ -0,0 +1,54 @@
|
||||
package immutable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/interceptor/immutable"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultBuilders = []interceptor.Builder{
|
||||
&manifestDeletionBuilder{},
|
||||
&manifestCreationBuilder{},
|
||||
}
|
||||
)
|
||||
|
||||
type manifestDeletionBuilder struct{}
|
||||
|
||||
func (*manifestDeletionBuilder) Build(req *http.Request) (interceptor.Interceptor, error) {
|
||||
if match, _, _ := util.MatchDeleteManifest(req); !match {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
info, ok := util.ManifestInfoFromContext(req.Context())
|
||||
if !ok {
|
||||
var err error
|
||||
info, err = util.ParseManifestInfoFromPath(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse manifest, error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return immutable.NewDeleteMFInteceptor(info), nil
|
||||
}
|
||||
|
||||
type manifestCreationBuilder struct{}
|
||||
|
||||
func (*manifestCreationBuilder) Build(req *http.Request) (interceptor.Interceptor, error) {
|
||||
if match, _, _ := util.MatchPushManifest(req); !match {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
info, ok := util.ManifestInfoFromContext(req.Context())
|
||||
if !ok {
|
||||
var err error
|
||||
info, err = util.ParseManifestInfoFromReq(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse manifest, error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return immutable.NewPushMFInteceptor(info), nil
|
||||
}
|
@ -16,78 +16,74 @@ package immutable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
common_util "github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
"github.com/goharbor/harbor/src/pkg/art"
|
||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||
middlerware_err "github.com/goharbor/harbor/src/core/middlewares/util/error"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type immutableHandler struct {
|
||||
next http.Handler
|
||||
builders []interceptor.Builder
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
// New ...
|
||||
func New(next http.Handler) http.Handler {
|
||||
func New(next http.Handler, builders ...interceptor.Builder) http.Handler {
|
||||
if len(builders) == 0 {
|
||||
builders = defaultBuilders
|
||||
}
|
||||
|
||||
return &immutableHandler{
|
||||
next: next,
|
||||
builders: builders,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP ...
|
||||
func (rh immutableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if match, _, _ := util.MatchPushManifest(req); !match {
|
||||
func (rh *immutableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
interceptor, err := rh.getInterceptor(req)
|
||||
if err != nil {
|
||||
log.Warningf("Error occurred when to handle request in immutable handler: %v", err)
|
||||
http.Error(rw, util.MarshalError("InternalError", fmt.Sprintf("Error occurred when to handle request in immutable handler: %v", err)),
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if interceptor == nil {
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
info, ok := util.ManifestInfoFromContext(req.Context())
|
||||
if !ok {
|
||||
var err error
|
||||
info, err = util.ParseManifestInfoFromPath(req)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
|
||||
if err := interceptor.HandleRequest(req); err != nil {
|
||||
log.Warningf("Error occurred when to handle request in immutable handler: %v", err)
|
||||
if _, ok := err.(middlerware_err.ErrImmutable); ok {
|
||||
http.Error(rw, util.MarshalError("DENIED",
|
||||
fmt.Sprintf("The tag is immutable, cannot be overwrite: %v", err)), http.StatusPreconditionFailed)
|
||||
return
|
||||
}
|
||||
http.Error(rw, util.MarshalError("InternalError", fmt.Sprintf("Error occurred when to handle request in immutable handler: %v", err)),
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
|
||||
interceptor.HandleResponse(rw, req)
|
||||
}
|
||||
|
||||
func (rh *immutableHandler) getInterceptor(req *http.Request) (interceptor.Interceptor, error) {
|
||||
for _, builder := range rh.builders {
|
||||
interceptor, err := builder.Build(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if interceptor != nil {
|
||||
return interceptor, nil
|
||||
}
|
||||
}
|
||||
|
||||
_, repoName := common_util.ParseRepository(info.Repository)
|
||||
matched, err := rule.NewRuleMatcher(info.ProjectID).Match(art.Candidate{
|
||||
Repository: repoName,
|
||||
Tag: info.Tag,
|
||||
NamespaceID: info.ProjectID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
if !matched {
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
artifactQuery := &models.ArtifactQuery{
|
||||
PID: info.ProjectID,
|
||||
Repo: info.Repository,
|
||||
Tag: info.Tag,
|
||||
}
|
||||
afs, err := dao.ListArtifacts(artifactQuery)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
if len(afs) == 0 {
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
// rule matched and non-existent is a immutable tag
|
||||
http.Error(rw, util.MarshalError("DENIED",
|
||||
fmt.Sprintf("The tag:%s:%s is immutable, cannot be overwrite.", info.Repository, info.Tag)), http.StatusPreconditionFailed)
|
||||
return
|
||||
return nil, nil
|
||||
}
|
||||
|
65
src/core/middlewares/interceptor/immutable/deletemf.go
Normal file
65
src/core/middlewares/interceptor/immutable/deletemf.go
Normal file
@ -0,0 +1,65 @@
|
||||
package immutable
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
common_util "github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
middlerware_err "github.com/goharbor/harbor/src/core/middlewares/util/error"
|
||||
"github.com/goharbor/harbor/src/pkg/art"
|
||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewDeleteMFInteceptor ....
|
||||
func NewDeleteMFInteceptor(mf *util.ManifestInfo) interceptor.Interceptor {
|
||||
return &delmfInterceptor{
|
||||
mf: mf,
|
||||
}
|
||||
}
|
||||
|
||||
type delmfInterceptor struct {
|
||||
mf *util.ManifestInfo
|
||||
}
|
||||
|
||||
// HandleRequest ...
|
||||
func (dmf *delmfInterceptor) HandleRequest(req *http.Request) (err error) {
|
||||
|
||||
artifactQuery := &models.ArtifactQuery{
|
||||
Digest: dmf.mf.Digest,
|
||||
}
|
||||
var afs []*models.Artifact
|
||||
afs, err = dao.ListArtifacts(artifactQuery)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if len(afs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, af := range afs {
|
||||
_, repoName := common_util.ParseRepository(dmf.mf.Repository)
|
||||
var matched bool
|
||||
matched, err = rule.NewRuleMatcher(dmf.mf.ProjectID).Match(art.Candidate{
|
||||
Repository: repoName,
|
||||
Tag: af.Tag,
|
||||
NamespaceID: dmf.mf.ProjectID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if matched {
|
||||
return middlerware_err.NewErrImmutable(repoName)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// HandleRequest ...
|
||||
func (dmf *delmfInterceptor) HandleResponse(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
65
src/core/middlewares/interceptor/immutable/pushmf.go
Normal file
65
src/core/middlewares/interceptor/immutable/pushmf.go
Normal file
@ -0,0 +1,65 @@
|
||||
package immutable
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
common_util "github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
middlerware_err "github.com/goharbor/harbor/src/core/middlewares/util/error"
|
||||
"github.com/goharbor/harbor/src/pkg/art"
|
||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewPushMFInteceptor ....
|
||||
func NewPushMFInteceptor(mf *util.ManifestInfo) interceptor.Interceptor {
|
||||
return &pushmfInterceptor{
|
||||
mf: mf,
|
||||
}
|
||||
}
|
||||
|
||||
type pushmfInterceptor struct {
|
||||
mf *util.ManifestInfo
|
||||
}
|
||||
|
||||
// HandleRequest ...
|
||||
func (pmf *pushmfInterceptor) HandleRequest(req *http.Request) (err error) {
|
||||
|
||||
_, repoName := common_util.ParseRepository(pmf.mf.Repository)
|
||||
var matched bool
|
||||
matched, err = rule.NewRuleMatcher(pmf.mf.ProjectID).Match(art.Candidate{
|
||||
Repository: repoName,
|
||||
Tag: pmf.mf.Tag,
|
||||
NamespaceID: pmf.mf.ProjectID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if !matched {
|
||||
return
|
||||
}
|
||||
|
||||
artifactQuery := &models.ArtifactQuery{
|
||||
PID: pmf.mf.ProjectID,
|
||||
Repo: pmf.mf.Repository,
|
||||
Tag: pmf.mf.Tag,
|
||||
}
|
||||
var afs []*models.Artifact
|
||||
afs, err = dao.ListArtifacts(artifactQuery)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if len(afs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
return middlerware_err.NewErrImmutable(repoName)
|
||||
}
|
||||
|
||||
// HandleRequest ...
|
||||
func (pmf *pushmfInterceptor) HandleResponse(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
20
src/core/middlewares/util/error/immutable.go
Normal file
20
src/core/middlewares/util/error/immutable.go
Normal file
@ -0,0 +1,20 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrImmutable ...
|
||||
type ErrImmutable struct {
|
||||
repo string
|
||||
}
|
||||
|
||||
// Error ...
|
||||
func (ei ErrImmutable) Error() string {
|
||||
return fmt.Sprintf("Failed to process request, due to immutable. '%s'", ei.repo)
|
||||
}
|
||||
|
||||
// NewErrImmutable ...
|
||||
func NewErrImmutable(msg string) ErrImmutable {
|
||||
return ErrImmutable{repo: msg}
|
||||
}
|
@ -17,7 +17,6 @@ package whitelist
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type invalidErr struct {
|
||||
@ -46,11 +45,12 @@ const cveIDPattern = `^CVE-\d{4}-\d+$`
|
||||
// Validate help validates the CVE whitelist, to ensure the CVE ID is valid and there's no duplication
|
||||
func Validate(wl models.CVEWhitelist) error {
|
||||
m := map[string]struct{}{}
|
||||
re := regexp.MustCompile(cveIDPattern)
|
||||
// re := regexp.MustCompile(cveIDPattern)
|
||||
for _, it := range wl.Items {
|
||||
if !re.MatchString(it.CVEID) {
|
||||
return &invalidErr{fmt.Sprintf("invalid CVE ID: %s", it.CVEID)}
|
||||
}
|
||||
// Bypass the cve format checking
|
||||
// if !re.MatchString(it.CVEID) {
|
||||
// return &invalidErr{fmt.Sprintf("invalid CVE ID: %s", it.CVEID)}
|
||||
// }
|
||||
if _, ok := m[it.CVEID]; ok {
|
||||
return &invalidErr{fmt.Sprintf("duplicate CVE ID in whitelist: %s", it.CVEID)}
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ func TestValidate(t *testing.T) {
|
||||
l: models.CVEWhitelist{
|
||||
Items: []models.CVEWhitelistItem{
|
||||
{CVEID: "breakit"},
|
||||
{CVEID: "breakit"},
|
||||
},
|
||||
},
|
||||
noError: false,
|
||||
|
103
src/portal/lib/src/cache/index.ts
vendored
Normal file
103
src/portal/lib/src/cache/index.ts
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
// 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.
|
||||
|
||||
import { Observable, of } from "rxjs";
|
||||
import { tap, publishReplay, refCount } from "rxjs/operators";
|
||||
|
||||
function hashCode(str: string): string {
|
||||
let hash: number = 0;
|
||||
let chr: number;
|
||||
if (str.length === 0) {
|
||||
return hash.toString(36);
|
||||
}
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
chr = str.charCodeAt(i);
|
||||
|
||||
/* tslint:disable:no-bitwise */
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash |= 0;
|
||||
/* tslint:enable:no-bitwise */
|
||||
}
|
||||
|
||||
return hash.toString(36);
|
||||
}
|
||||
|
||||
interface IObservableCacheValue {
|
||||
response: Observable<any>;
|
||||
|
||||
/**
|
||||
* created time of the cache value
|
||||
*/
|
||||
created?: Date;
|
||||
}
|
||||
|
||||
interface IObservableCacheConfig {
|
||||
/**
|
||||
* maxAge of cache in milliseconds
|
||||
*/
|
||||
maxAge?: number;
|
||||
|
||||
/**
|
||||
* whether should use sliding expiration on caches
|
||||
*/
|
||||
slidingExpiration?: boolean;
|
||||
}
|
||||
|
||||
const cache: Map<string, IObservableCacheValue> = new Map<string, IObservableCacheValue>();
|
||||
|
||||
export function CacheObservable(config: IObservableCacheConfig = {}) {
|
||||
return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
|
||||
const original = descriptor.value;
|
||||
const targetName = target.constructor.name;
|
||||
|
||||
(descriptor.value as any) = function (...args: Array<any>) {
|
||||
const key = hashCode(`${targetName}:${methodName}:${JSON.stringify(args)}`);
|
||||
|
||||
let value = cache.get(key);
|
||||
if (value && value.created) {
|
||||
if (new Date().getTime() - new Date(value.created).getTime() > config.maxAge) {
|
||||
cache[key] = null;
|
||||
value = null;
|
||||
} else if (config.slidingExpiration) {
|
||||
value.created = new Date();
|
||||
cache.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (value) {
|
||||
return of(value.response);
|
||||
}
|
||||
|
||||
const response$ = (original.apply(this, args) as Observable<any>).pipe(
|
||||
tap((response: Observable<any>) => {
|
||||
cache.set(key, {
|
||||
response: response,
|
||||
created: config.maxAge ? new Date() : null
|
||||
});
|
||||
}),
|
||||
publishReplay(1),
|
||||
refCount()
|
||||
);
|
||||
|
||||
return response$;
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
export function FlushAll() {
|
||||
cache.clear();
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
<clr-tab>
|
||||
<button id="config-vulnerability" clrTabLink>{{'CONFIG.VULNERABILITY' | translate}}</button>
|
||||
<clr-tab-content id="vulnerability" *clrIfActive>
|
||||
<vulnerability-config *ngIf="withClair" #vulnerabilityConfig [showSubTitle]="true"></vulnerability-config>
|
||||
<vulnerability-config #vulnerabilityConfig [showSubTitle]="true"></vulnerability-config>
|
||||
</clr-tab-content>
|
||||
</clr-tab>
|
||||
<clr-tab>
|
||||
@ -21,4 +21,4 @@
|
||||
</clr-tab-content>
|
||||
</clr-tab>
|
||||
</clr-tabs>
|
||||
<confirmation-dialog #cfgConfirmationDialog (confirmAction)="confirmCancel($event)"></confirmation-dialog>
|
||||
<confirmation-dialog #cfgConfirmationDialog (confirmAction)="confirmCancel($event)"></confirmation-dialog>
|
||||
|
@ -18,4 +18,7 @@
|
||||
|
||||
.replication-tooltip {
|
||||
top: -8px;
|
||||
}
|
||||
}
|
||||
.margin-top-3px {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
@ -73,7 +73,7 @@
|
||||
</clr-checkbox-container>
|
||||
|
||||
|
||||
<div class="clr-form-control d-f" *ngIf="withClair">
|
||||
<div class="clr-form-control d-f">
|
||||
<label for="systemWhitelist"
|
||||
class="clr-control-label">{{'CVE_WHITELIST.DEPLOYMENT_SECURITY'|translate}}</label>
|
||||
<div class="form-content">
|
||||
@ -172,4 +172,4 @@
|
||||
[disabled]="(!isValid() || !hasChanges()) && (!hasWhitelistChanged) || inProgress">{{'BUTTON.CANCEL'
|
||||
| translate}}</button>
|
||||
</div>
|
||||
<confirmation-dialog #cfgConfirmationDialog (confirmAction)="confirmCancel($event)"></confirmation-dialog>
|
||||
<confirmation-dialog #cfgConfirmationDialog (confirmAction)="confirmCancel($event)"></confirmation-dialog>
|
||||
|
@ -118,7 +118,7 @@
|
||||
<clr-tooltip-content clrPosition="top-left" clrSize="md" *clrIfOpen>
|
||||
<span class="tooltip-content" *ngIf="supportedFilters[i]?.type==='name'">{{'TOOLTIP.NAME_FILTER' | translate}}</span>
|
||||
<span class="tooltip-content" *ngIf="supportedFilters[i]?.type==='tag'">{{'TOOLTIP.TAG_FILTER' | translate}}</span>
|
||||
<span class="tooltip-content" *ngIf="supportedFilters[i]?.type==='label'">{{'TOOLTIP.LABEL_FILTER' | translate}}</span>
|
||||
<span class="tooltip-content" >{{'TOOLTIP.LABEL_FILTER' | translate}}</span>
|
||||
<span class="tooltip-content" *ngIf="supportedFilters[i]?.type==='resource'">{{'TOOLTIP.RESOURCE_FILTER' | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
@ -209,7 +209,7 @@
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
</div>
|
||||
<div class="clr-checkbox-wrapper clr-form-control">
|
||||
<div class="clr-checkbox-wrapper clr-form-control" [hidden]="policyId < 0">
|
||||
<input type="checkbox" [checked]="true" id="enablePolicy" formControlName="enabled" class="clr-checkbox">
|
||||
<label for="enablePolicy" class="clr-control-label">{{'REPLICATION.ENABLED_RULE' | translate}}</label>
|
||||
</div>
|
||||
|
@ -203,8 +203,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
}),
|
||||
filters: this.fb.array([]),
|
||||
deletion: false,
|
||||
enabled: true,
|
||||
deletion: false,
|
||||
override: true
|
||||
});
|
||||
}
|
||||
@ -253,7 +253,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
dest_registry: rule.dest_registry,
|
||||
trigger: rule.trigger,
|
||||
deletion: rule.deletion,
|
||||
enabled: rule.enabled,
|
||||
enabled: true,
|
||||
override: rule.override
|
||||
});
|
||||
let filtersArray = this.getFilterArray(rule);
|
||||
|
@ -29,4 +29,5 @@ export * from "./repository-gridview/index";
|
||||
export * from "./operation/index";
|
||||
export * from "./_animations/index";
|
||||
export * from "./cron-schedule/index";
|
||||
export * from "./cache/index";
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
<clr-control-helper class="config-subtext"> {{ 'PROJECT_CONFIG.PUBLIC_POLICY' | translate }}
|
||||
</clr-control-helper>
|
||||
</clr-checkbox-container>
|
||||
<clr-checkbox-container *ngIf="withNotary || withClair">
|
||||
<clr-checkbox-container *ngIf="withNotary">
|
||||
<label><span *ngIf="withNotary">{{ 'PROJECT_CONFIG.SECURITY' | translate }}</span></label>
|
||||
<clr-checkbox-wrapper *ngIf="withNotary">
|
||||
<input type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.ContentTrust" name="content-trust"
|
||||
@ -20,9 +20,9 @@
|
||||
<clr-control-helper class="config-subtext"> {{ 'PROJECT_CONFIG.CONTENT_TRUST_POLCIY' | translate }}
|
||||
</clr-control-helper>
|
||||
</clr-checkbox-container>
|
||||
<clr-checkbox-container id="prevent-vulenrability-image" *ngIf="withNotary || withClair">
|
||||
<clr-checkbox-container id="prevent-vulenrability-image" *ngIf="withNotary">
|
||||
<label><span *ngIf="!withNotary">{{ 'PROJECT_CONFIG.SECURITY' | translate }}</span></label>
|
||||
<clr-checkbox-wrapper *ngIf="withClair">
|
||||
<clr-checkbox-wrapper>
|
||||
<input type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.PreventVulImg"
|
||||
name="prevent-vulenrability-image-input" [disabled]="!hasChangeConfigRole" />
|
||||
<label>{{ 'PROJECT_CONFIG.PREVENT_VULNERABLE_TOGGLE' | translate }}</label>
|
||||
@ -46,7 +46,7 @@
|
||||
</div>
|
||||
</clr-control-helper>
|
||||
</clr-checkbox-container>
|
||||
<clr-checkbox-container *ngIf="withClair">
|
||||
<clr-checkbox-container>
|
||||
<label>{{ 'PROJECT_CONFIG.SCAN' | translate }}</label>
|
||||
<clr-checkbox-wrapper id="scan-image-on-push-wrapper">
|
||||
<input type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.ScanImgOnPush"
|
||||
@ -56,7 +56,7 @@
|
||||
<clr-control-helper class="config-subtext"> {{ 'PROJECT_CONFIG.AUTOSCAN_POLICY' | translate }}
|
||||
</clr-control-helper>
|
||||
</clr-checkbox-container>
|
||||
<div class="clr-form-control" [class.clr-form-control-disabled]="!hasChangeConfigRole" *ngIf="withClair">
|
||||
<div class="clr-form-control" [class.clr-form-control-disabled]="!hasChangeConfigRole">
|
||||
<label for="systemWhitelist" class="clr-control-label">{{'CVE_WHITELIST.CVE_WHITELIST'|translate}}</label>
|
||||
<div class="w-100 clr-control-container">
|
||||
<div class="config-subtext">
|
||||
|
@ -55,10 +55,9 @@
|
||||
<section id="image" role="tabpanel" aria-labelledby="repo-image" [hidden]='!isCurrentTabContent("image")'>
|
||||
<div id=images-container>
|
||||
<hbr-tag ngProjectAs="clr-dg-row-detail" (tagClickEvent)="watchTagClickEvt($event)" (signatureOutput)="saveSignatures($event)"
|
||||
class="sub-grid-custom" [repoName]="repoName" [registryUrl]="registryUrl" [withNotary]="withNotary"
|
||||
[withClair]="withClair" [withAdmiral]="withAdmiral" [hasSignedIn]="hasSignedIn"
|
||||
class="sub-grid-custom" [repoName]="repoName" [registryUrl]="registryUrl" [withNotary]="withNotary" [withAdmiral]="withAdmiral" [hasSignedIn]="hasSignedIn"
|
||||
[isGuest]="isGuest" [projectId]="projectId" [memberRoleID]="memberRoleID"></hbr-tag>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
@ -68,6 +68,7 @@ export interface Tag extends Base {
|
||||
labels: Label[];
|
||||
push_time?: string;
|
||||
pull_time?: string;
|
||||
immutable?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,6 +53,12 @@ export const USERSTATICPERMISSION = {
|
||||
"READ": "read",
|
||||
}
|
||||
},
|
||||
"QUOTA": {
|
||||
"KEY": "quota",
|
||||
"VALUE": {
|
||||
"READ": "read"
|
||||
}
|
||||
},
|
||||
"REPOSITORY": {
|
||||
'KEY': 'repository',
|
||||
'VALUE': {
|
||||
|
@ -13,14 +13,16 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, throwError as observableThrowError } from "rxjs";
|
||||
import { map, catchError, shareReplay } from "rxjs/operators";
|
||||
import { UserPrivilegeServeItem } from './interface';
|
||||
import { Observable, forkJoin, of, throwError as observableThrowError } from "rxjs";
|
||||
import { map, tap, publishReplay, refCount } from "rxjs/operators";
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { CacheObservable } from '../cache';
|
||||
|
||||
interface Permission {
|
||||
resource: string;
|
||||
action: string;
|
||||
}
|
||||
|
||||
|
||||
const CACHE_SIZE = 1;
|
||||
/**
|
||||
* Get System privilege about current backend server.
|
||||
* @abstract
|
||||
@ -35,8 +37,11 @@ export abstract class UserPermissionService {
|
||||
*/
|
||||
abstract getPermission(projectId, resource, action);
|
||||
abstract clearPermissionCache();
|
||||
abstract hasProjectPermission(projectId: any, permission: Permission): Observable<boolean>;
|
||||
abstract hasProjectPermissions(projectId: any, permissions: Array<Permission>): Observable<Array<boolean>>;
|
||||
}
|
||||
|
||||
// @dynamic
|
||||
@Injectable()
|
||||
export class UserPermissionDefaultService extends UserPermissionService {
|
||||
constructor(
|
||||
@ -44,36 +49,37 @@ export class UserPermissionDefaultService extends UserPermissionService {
|
||||
) {
|
||||
super();
|
||||
}
|
||||
private permissionCache: Observable<object>;
|
||||
private projectId: number;
|
||||
private getPermissionFromBackend(projectId): Observable<object> {
|
||||
const userPermissionUrl = `/api/users/current/permissions?scope=/project/${projectId}&relative=true`;
|
||||
return this.http.get(userPermissionUrl);
|
||||
}
|
||||
private processingPermissionResult(responsePermission, resource, action): boolean {
|
||||
const permissionList = responsePermission as UserPrivilegeServeItem[];
|
||||
for (const privilegeItem of permissionList) {
|
||||
if (privilegeItem.resource === resource && privilegeItem.action === action) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public getPermission(projectId, resource, action): Observable<boolean> {
|
||||
|
||||
if (!this.permissionCache || this.projectId !== +projectId) {
|
||||
this.projectId = +projectId;
|
||||
this.permissionCache = this.getPermissionFromBackend(projectId).pipe(
|
||||
shareReplay(CACHE_SIZE));
|
||||
}
|
||||
return this.permissionCache.pipe(map(response => {
|
||||
return this.processingPermissionResult(response, resource, action);
|
||||
}))
|
||||
.pipe(catchError(error => observableThrowError(error)
|
||||
@CacheObservable({ maxAge: 1000 * 60 })
|
||||
private getPermissions(scope: string, relative?: boolean): Observable<Array<Permission>> {
|
||||
const url = `/api/users/current/permissions?scope=${scope}&relative=${relative ? 'true' : 'false'}`;
|
||||
return this.http.get<Array<Permission>>(url);
|
||||
}
|
||||
|
||||
private hasPermission(permission: Permission, scope: string, relative?: boolean): Observable<boolean> {
|
||||
return this.getPermissions(scope, relative).pipe(map(
|
||||
(permissions: Array<Permission>) => {
|
||||
return permissions.some((p: Permission) => p.resource === permission.resource && p.action === permission.action);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
private hasPermissions(permissions: Array<Permission>, scope: string, relative?: boolean): Observable<Array<boolean>> {
|
||||
return forkJoin(permissions.map((permission) => this.hasPermission(permission, scope, relative)));
|
||||
}
|
||||
|
||||
public hasProjectPermission(projectId: any, permission: Permission): Observable<boolean> {
|
||||
return this.hasPermission(permission, `/project/${projectId}`, true);
|
||||
}
|
||||
|
||||
public hasProjectPermissions(projectId: any, permissions: Array<Permission>): Observable<Array<boolean>> {
|
||||
return this.hasPermissions(permissions, `/project/${projectId}`, true);
|
||||
}
|
||||
|
||||
public getPermission(projectId: any, resource: string, action: string): Observable<boolean> {
|
||||
return this.hasProjectPermission(projectId, { resource, action });
|
||||
}
|
||||
|
||||
public clearPermissionCache() {
|
||||
this.permissionCache = null;
|
||||
this.projectId = null;
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +145,11 @@ export const PROJECT_ROOTS = [
|
||||
NAME: "guest",
|
||||
VALUE: 3,
|
||||
LABEL: "GROUP.GUEST"
|
||||
},
|
||||
{
|
||||
NAME: "limited",
|
||||
VALUE: 5,
|
||||
LABEL: "GROUP.LIMITED_GUEST"
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -85,7 +85,7 @@
|
||||
<clr-dg-column *ngIf="withNotary">{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="createdComparator">{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'docker_version'" *ngIf="!withClair">{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'docker_version'">{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column *ngIf="!withAdmiral">{{'REPOSITORY.LABELS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="pushComparator">{{'REPOSITORY.PUSH_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="pullComparator">{{'REPOSITORY.PULL_TIME' | translate}}</clr-dg-column>
|
||||
@ -93,6 +93,7 @@
|
||||
<clr-dg-row *clrDgItems="let t of tags" [clrDgItem]='t'>
|
||||
<clr-dg-cell class="truncated flex-max-width">
|
||||
<a href="javascript:void(0)" (click)="onTagClick(t)" title="{{t.name}}">{{t.name}}</a>
|
||||
<span *ngIf="t.immutable" class="label label-info ml-1">{{'REPOSITORY.IMMUTABLE' | translate}}</span>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{sizeTransform(t.size)}}</clr-dg-cell>
|
||||
<clr-dg-cell class="truncated" title="docker pull {{registryUrl}}/{{repoName}}:{{t.name}}">
|
||||
@ -111,7 +112,7 @@
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell class="truncated" title="{{t.author}}">{{t.author}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.created | date: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="!withClair">{{t.docker_version}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.docker_version}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="!withAdmiral">
|
||||
<hbr-label-piece *ngIf="t.labels?.length" [label]="t.labels[0]" [labelWidth]="90"> </hbr-label-piece>
|
||||
<div class="signpost-item" [hidden]="t.labels?.length<=1">
|
||||
@ -136,4 +137,4 @@
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -723,9 +723,7 @@ export class TagComponent implements OnInit, AfterViewInit {
|
||||
canScanNow(t: Tag[]): boolean {
|
||||
if (!this.hasScanImagePermission) { return false; }
|
||||
let st: string = this.scanStatus(t[0]);
|
||||
|
||||
return st !== VULNERABILITY_SCAN_STATUS.PENDING &&
|
||||
st !== VULNERABILITY_SCAN_STATUS.RUNNING;
|
||||
return st !== VULNERABILITY_SCAN_STATUS.RUNNING;
|
||||
}
|
||||
getImagePermissionRule(projectId: number): void {
|
||||
let hasAddLabelImagePermission = this.userPermissionService.getPermission(projectId, USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.KEY,
|
||||
|
@ -19,7 +19,6 @@ clr-modal {
|
||||
align-items: center;
|
||||
.reset-cli {
|
||||
height: 30px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
.btn-padding-less {
|
||||
padding-left: 5px;
|
||||
|
@ -374,6 +374,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
}
|
||||
closeReset() {
|
||||
this.showSecretDetail = false;
|
||||
this.showGenerateCliFn();
|
||||
this.resetSecretFrom.resetForm(new ResetSecret());
|
||||
}
|
||||
}
|
||||
|
@ -43,12 +43,12 @@
|
||||
</a>
|
||||
</clr-vertical-nav-group-children>
|
||||
</clr-vertical-nav-group>
|
||||
<clr-vertical-nav-group *ngIf="isSystemAdmin && (withClair || hasAdminRole)" routerLinkActive="active">
|
||||
<clr-vertical-nav-group *ngIf="isSystemAdmin && hasAdminRole" routerLinkActive="active">
|
||||
<clr-icon shape="event" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.TASKS' | translate}}
|
||||
<a routerLink="#" hidden aria-hidden="true"></a>
|
||||
<clr-vertical-nav-group-children *clrIfExpanded="true">
|
||||
<a clrVerticalNavLink *ngIf="withClair" routerLink="/harbor/vulnerability"
|
||||
<a clrVerticalNavLink routerLink="/harbor/vulnerability"
|
||||
routerLinkActive="active">
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.VULNERABILITY' | translate}}
|
||||
</a>
|
||||
|
@ -17,18 +17,26 @@
|
||||
<button clrDropdownItem
|
||||
(click)="changeStat()"
|
||||
[disabled]="!(selectedRow && !selectedRow.is_default)">
|
||||
<span *ngIf="selectedRow && selectedRow.disabled">{{'BUTTON.ENABLE' | translate}}</span>
|
||||
<span *ngIf="!(selectedRow && selectedRow.disabled)">{{'BUTTON.DISABLE' | translate}}</span>
|
||||
<span *ngIf="selectedRow && selectedRow.disabled">
|
||||
<clr-icon class="margin-top-2" size="16" shape="success-standard"></clr-icon>
|
||||
<span class="margin-left-10">{{'SCANNER.ENABLE' | translate}}</span>
|
||||
</span>
|
||||
<span *ngIf="!(selectedRow && selectedRow.disabled)">
|
||||
<clr-icon class="margin-top-2" size="16" shape="ban"></clr-icon>
|
||||
<span class="margin-left-10">{{'SCANNER.DISABLE' | translate}}</span>
|
||||
</span>
|
||||
</button>
|
||||
<button clrDropdownItem
|
||||
(click)="editScanner()"
|
||||
class="btn btn-sm btn-secondary" [disabled]="!selectedRow">
|
||||
{{'BUTTON.EDIT' | translate}}
|
||||
<clr-icon class="margin-top-0" size="16" shape="pencil"></clr-icon>
|
||||
<span class="margin-left-10">{{'BUTTON.EDIT' | translate}}</span>
|
||||
</button>
|
||||
<button clrDropdownItem
|
||||
(click)="deleteScanners()"
|
||||
class="btn btn-sm btn-secondary" [disabled]="!selectedRow">
|
||||
{{'BUTTON.DELETE' | translate}}
|
||||
<clr-icon class="margin-top-0" size="16" shape="times"></clr-icon>
|
||||
<span class="margin-left-10">{{'BUTTON.DELETE' | translate}}</span>
|
||||
</button>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
@ -45,7 +53,7 @@
|
||||
<clr-dg-column class="width-240" [clrDgField]="'name'">{{'SCANNER.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column class="width-240" [clrDgField]="'url'">{{'SCANNER.ENDPOINT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SCANNER.HEALTH' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SCANNER.DISABLED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SCANNER.ENABLED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SCANNER.AUTH' | translate}}</clr-dg-column>
|
||||
<clr-dg-placeholder>
|
||||
{{'SCANNER.NO_SCANNER' | translate}}
|
||||
@ -62,7 +70,7 @@
|
||||
<span class="label label-danger">{{'SCANNER.UNHEALTHY' | translate}}</span>
|
||||
</ng-template>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{scanner.disabled}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{!scanner.disabled}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{scanner.auth?scanner.auth:'None'}}</clr-dg-cell>
|
||||
<scanner-metadata *clrIfExpanded [uid]="scanner.uuid" ngProjectAs="clr-dg-row-detail"></scanner-metadata>
|
||||
</clr-dg-row>
|
||||
|
@ -22,3 +22,12 @@
|
||||
.width-240 {
|
||||
min-width: 240px !important;
|
||||
}
|
||||
.margin-top-0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.margin-top-2 {
|
||||
margin-top: 2px;
|
||||
}
|
||||
.margin-left-10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy {
|
||||
confirmed.state === ConfirmationState.CONFIRMED) {
|
||||
this.configScannerService.deleteScanners(confirmed.data)
|
||||
.subscribe(response => {
|
||||
this.msgHandler.showSuccess("Delete Success");
|
||||
this.msgHandler.showSuccess("SCANNER.DELETE_SUCCESS");
|
||||
this.getScanners();
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
@ -76,7 +76,7 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy {
|
||||
scanner.disabled = !scanner.disabled;
|
||||
this.configScannerService.updateScanner(scanner)
|
||||
.subscribe(response => {
|
||||
this.msgHandler.showSuccess("Update Success");
|
||||
this.msgHandler.showSuccess("SCANNER.UPDATE_SUCCESS");
|
||||
this.getScanners();
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
@ -87,7 +87,7 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy {
|
||||
if (this.selectedRow) {
|
||||
this.configScannerService.setAsDefault(this.selectedRow.uuid)
|
||||
.subscribe(response => {
|
||||
this.msgHandler.showSuccess("Update Success");
|
||||
this.msgHandler.showSuccess("SCANNER.UPDATE_SUCCESS");
|
||||
this.getScanners();
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
|
@ -4,7 +4,7 @@
|
||||
<label class="required clr-control-label">{{"SCANNER.NAME" | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="!isNameValid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input autocomplete="off" #name formControlName="name" class="clr-input width-312"
|
||||
<input autocomplete="off" #name formControlName="name" class="clr-input width-280"
|
||||
type="text"
|
||||
id="scanner-name">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
@ -18,7 +18,7 @@
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label">{{"SCANNER.DESCRIPTION" | translate}}</label>
|
||||
<div class="clr-control-container">
|
||||
<textarea autocomplete="off" formControlName="description" class="clr-textarea width-312" type="text"
|
||||
<textarea autocomplete="off" formControlName="description" class="clr-textarea width-280" type="text"
|
||||
id="description">
|
||||
</textarea>
|
||||
</div>
|
||||
@ -28,7 +28,7 @@
|
||||
<div class="clr-control-container" [class.clr-error]="!isEndpointValid || showEndpointError">
|
||||
<div class="clr-input-wrapper">
|
||||
<input (focus)="showEndpointError=false" (blur)="checkEndpointUrl()" #endpointUrl placeholder="http(s)://192.168.1.1" autocomplete="off" formControlName="url"
|
||||
class="clr-input width-312" type="text" id="scanner-endpoint">
|
||||
class="clr-input width-280" type="text" id="scanner-endpoint">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkEndpointOnGoing"></span>
|
||||
</div>
|
||||
@ -41,7 +41,7 @@
|
||||
<label class="clr-control-label">{{"SCANNER.AUTH" | translate}}</label>
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-select-wrapper">
|
||||
<select formControlName="auth" class="clr-select width-312" id="scanner-authorization">
|
||||
<select formControlName="auth" class="clr-select width-280" id="scanner-authorization">
|
||||
<option value="None">{{"SCANNER.NONE" | translate}}</option>
|
||||
<option value="Basic">{{"SCANNER.BASIC" | translate}}</option>
|
||||
<option value="Bearer">{{"SCANNER.BEARER" | translate}}</option>
|
||||
@ -56,7 +56,7 @@
|
||||
<div class="clr-control-container" [class.clr-error]="!isUserNameValid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input formControlName="username" autocomplete="off"
|
||||
class="clr-input width-312" type="text" id="scanner-username">
|
||||
class="clr-input width-280" type="text" id="scanner-username">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error *ngIf="!isUserNameValid">
|
||||
@ -69,7 +69,7 @@
|
||||
<div class="clr-control-container" [class.clr-error]="!isPasswordValid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input formControlName="password" autocomplete="off"
|
||||
class="clr-input width-312" type="password" id="scanner-password">
|
||||
class="clr-input width-280" type="password" id="scanner-password">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error *ngIf="!isPasswordValid">
|
||||
@ -82,7 +82,7 @@
|
||||
<div class="clr-control-container" [class.clr-error]="!isTokenValid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input formControlName="token" autocomplete="off"
|
||||
class="clr-input width-312" type="text" id="scanner-token">
|
||||
class="clr-input width-280" type="text" id="scanner-token">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error *ngIf="!isTokenValid">
|
||||
@ -95,7 +95,7 @@
|
||||
<div class="clr-control-container" [class.clr-error]="!isApiKeyValid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input formControlName="apiKey" autocomplete="off"
|
||||
class="clr-input width-312" type="text" id="scanner-apiKey">
|
||||
class="clr-input width-280" type="text" id="scanner-apiKey">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error *ngIf="!isApiKeyValid">
|
||||
|
@ -1,6 +1,9 @@
|
||||
.width-312 {
|
||||
width: 312px;
|
||||
.width-280 {
|
||||
width: 280px;
|
||||
}
|
||||
.padding-top-3 {
|
||||
padding-top: 3px;
|
||||
}
|
||||
.clr-control-label {
|
||||
width: 9rem !important;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro
|
||||
checkOnGoing: boolean = false;
|
||||
newScannerForm: FormGroup = this.fb.group({
|
||||
name: this.fb.control("",
|
||||
[Validators.required, Validators.pattern(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/)]),
|
||||
[Validators.required]),
|
||||
description: this.fb.control(""),
|
||||
url: this.fb.control("",
|
||||
[Validators.required,
|
||||
@ -57,7 +57,7 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro
|
||||
if (this.isEdit && this.originValue && this.originValue.name === name) {
|
||||
return false;
|
||||
}
|
||||
return this.newScannerForm.get('name').valid && name.length > 1;
|
||||
return this.newScannerForm.get('name').valid && name.length > 0;
|
||||
}),
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
@ -130,15 +130,15 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro
|
||||
return true;
|
||||
}
|
||||
if (this.isNameExisting) {
|
||||
this.nameTooltip = 'NAME_EXISTS';
|
||||
this.nameTooltip = 'SCANNER.NAME_EXISTS';
|
||||
return false;
|
||||
}
|
||||
if (this.newScannerForm.get('name').errors && this.newScannerForm.get('name').errors.required) {
|
||||
this.nameTooltip = 'NAME_REQUIRED';
|
||||
this.nameTooltip = 'SCANNER.NAME_REQUIRED';
|
||||
return false;
|
||||
}
|
||||
if (this.newScannerForm.get('name').errors && this.newScannerForm.get('name').errors.pattern) {
|
||||
this.nameTooltip = 'NAME_REX';
|
||||
this.nameTooltip = 'SCANNER.NAME_REX';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -151,11 +151,11 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro
|
||||
return true;
|
||||
}
|
||||
if (this.isEndpointUrlExisting) {
|
||||
this.endpointTooltip = 'ENDPOINT_EXISTS';
|
||||
this.endpointTooltip = 'SCANNER.ENDPOINT_EXISTS';
|
||||
return false;
|
||||
}
|
||||
if (this.newScannerForm.get('url').errors && this.newScannerForm.get('url').errors.required) {
|
||||
this.endpointTooltip = 'ENDPOINT_REQUIRED';
|
||||
this.endpointTooltip = 'SCANNER.ENDPOINT_REQUIRED';
|
||||
return false;
|
||||
}
|
||||
// skip here, validate when onblur
|
||||
@ -167,7 +167,7 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro
|
||||
// validate endpointUrl when onblur
|
||||
checkEndpointUrl() {
|
||||
if (this.newScannerForm.get('url').errors && this.newScannerForm.get('url').errors.pattern) {
|
||||
this.endpointTooltip = "ILLEGAL_ENDPOINT";
|
||||
this.endpointTooltip = "SCANNER.ILLEGAL_ENDPOINT";
|
||||
this.showEndpointError = true;
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,10 @@
|
||||
<input clrRadio type="radio" name="member_role" id="checkrads_guest" [value]=3 [(ngModel)]="member.role_id">
|
||||
<label for="checkrads_guest">{{'MEMBER.GUEST' | translate}}</label>
|
||||
</clr-radio-wrapper>
|
||||
<clr-radio-wrapper>
|
||||
<input clrRadio type="radio" name="member_role" id="checkrads_limited_guest" [value]=5 [(ngModel)]="member.role_id">
|
||||
<label for="checkrads_limited_guest">{{'MEMBER.LIMITED_GUEST' | translate}}</label>
|
||||
</clr-radio-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -27,6 +27,7 @@
|
||||
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 4)" [disabled]="!(selectedRow.length && hasUpdateMemberPermission) || onlySelf">{{'MEMBER.PROJECT_MASTER' | translate}}</button>
|
||||
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 2)" [disabled]="!(selectedRow.length && hasUpdateMemberPermission) || onlySelf">{{'MEMBER.DEVELOPER' | translate}}</button>
|
||||
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 3)" [disabled]="!(selectedRow.length && hasUpdateMemberPermission) || onlySelf">{{'MEMBER.GUEST' | translate}}</button>
|
||||
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 5)" [disabled]="!(selectedRow.length && hasUpdateMemberPermission) || onlySelf">{{'MEMBER.LIMITED_GUEST' | translate}}</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button clrDropdownItem (click)="openDeleteMembersDialog(selectedRow)" [disabled]="!(selectedRow.length && hasDeleteMemberPermission) || onlySelf">{{'MEMBER.REMOVE' | translate}}</button>
|
||||
</clr-dropdown-menu>
|
||||
|
@ -256,7 +256,7 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
// Function to delete specific member
|
||||
let deleteMember = (projectId: number, member: Member) => {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'OPERATION.DELETE_MEMBER';
|
||||
operMessage.name = member.entity_type === 'u' ? 'OPERATION.DELETE_MEMBER' : 'OPERATION.DELETE_GROUP';
|
||||
operMessage.data.id = member.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = member.entity_name;
|
||||
|
@ -79,7 +79,7 @@
|
||||
[(ngModel)]="imagePermissionPull" clrCheckbox>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr *ngIf="withHelmChart">
|
||||
<td class="left">{{'ROBOT_ACCOUNT.PERMISSIONS_HELMCHART' | translate}}</td>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
|
@ -8,6 +8,7 @@ import { of } from "rxjs";
|
||||
import { ErrorHandler } from "@harbor/ui";
|
||||
import { MessageHandlerService } from "../../../shared/message-handler/message-handler.service";
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { AppConfigService } from "../../../app-config.service";
|
||||
|
||||
describe('AddRobotComponent', () => {
|
||||
let component: AddRobotComponent;
|
||||
@ -24,6 +25,13 @@ describe('AddRobotComponent', () => {
|
||||
let fakeMessageHandlerService = {
|
||||
showSuccess: function() {}
|
||||
};
|
||||
let fakeAppConfigService = {
|
||||
getConfig: function() {
|
||||
return {
|
||||
with_chartmuseum: true
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -40,6 +48,7 @@ describe('AddRobotComponent', () => {
|
||||
TranslateService,
|
||||
ErrorHandler,
|
||||
{ provide: MessageHandlerService, useValue: fakeMessageHandlerService },
|
||||
{ provide: AppConfigService, useValue: fakeAppConfigService },
|
||||
{ provide: RobotService, useValue: fakeRobotService }
|
||||
]
|
||||
}).compileComponents();
|
||||
|
@ -18,6 +18,7 @@ import { ErrorHandler } from "@harbor/ui";
|
||||
import { MessageHandlerService } from "../../../shared/message-handler/message-handler.service";
|
||||
import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component";
|
||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||
import { AppConfigService } from "../../../app-config.service";
|
||||
|
||||
@Component({
|
||||
selector: "add-robot",
|
||||
@ -43,6 +44,7 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
robotForm: NgForm;
|
||||
imagePermissionPush: boolean = true;
|
||||
imagePermissionPull: boolean = true;
|
||||
withHelmChart: boolean;
|
||||
@Input() projectId: number;
|
||||
@Input() projectName: string;
|
||||
@Output() create = new EventEmitter<boolean>();
|
||||
@ -54,10 +56,13 @@ export class AddRobotComponent implements OnInit, OnDestroy {
|
||||
private errorHandler: ErrorHandler,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private messageHandlerService: MessageHandlerService,
|
||||
private sanitizer: DomSanitizer
|
||||
) {}
|
||||
private sanitizer: DomSanitizer,
|
||||
private appConfigService: AppConfigService
|
||||
|
||||
) {}
|
||||
ngOnInit(): void {
|
||||
this.withHelmChart = this.appConfigService.getConfig().with_chartmuseum;
|
||||
|
||||
this.robotNameChecker.pipe(debounceTime(800)).subscribe((name: string) => {
|
||||
let cont = this.currentForm.controls["robot_name"];
|
||||
if (cont) {
|
||||
|
@ -5,7 +5,7 @@
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label name">{{'SCANNER.SCANNER' | translate}}</label>
|
||||
<div class="clr-control-container">
|
||||
<button *ngIf="scanners && scanners.length > 0" id="edit-scanner" class="btn btn-link edit" (click)="open()">{{'SCANNER.EDIT' | translate}}</button>
|
||||
|
||||
<label *ngIf="!(scanners && scanners.length > 0)" class="name">{{'SCANNER.NOT_AVAILABLE' | translate}}</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -16,9 +16,10 @@
|
||||
<div class="clr-input-wrapper">
|
||||
<div class="clr-input-wrapper">
|
||||
<span id="scanner-name" class="scanner-name">{{scanner?.name}}</span>
|
||||
<button *ngIf="scanners && scanners.length > 0" id="edit-scanner" class="btn btn-primary btn-sm" (click)="open()">{{'SCANNER.EDIT' | translate}}</button>
|
||||
<span *ngIf="scanner?.disabled" class="label label-warning ml-1">{{'SCANNER.DISABLED' | translate}}</span>
|
||||
<span *ngIf="scanner?.health" class="label label-success ml-1">{{'SCANNER.HEALTHY' | translate}}</span>
|
||||
<span *ngIf="!scanner?.health" class="label label-danger ml-1">{{'SCANNER.Unhealthy' | translate}}</span>
|
||||
<span *ngIf="!scanner?.health" class="label label-danger ml-1">{{'SCANNER.UNHEALTHY' | translate}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,3 +18,6 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.clr-form-control {
|
||||
margin-top: 0.75rem !important;
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
<li>{{summaryInformation?.chart_count}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="display-flex project-detail pt-1">
|
||||
<div *ngIf="showProjectMemberInfo" class="display-flex project-detail pt-1">
|
||||
<h5 class="mt-0">{{'SUMMARY.PROJECT_MEMBER' | translate}}</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li>{{ summaryInformation?.project_admin_count }} {{'SUMMARY.ADMIN' | translate}}</li>
|
||||
@ -22,7 +22,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-right pt-1">
|
||||
<div *ngIf="showQuotaInfo" class="summary-right pt-1">
|
||||
<div class="display-flex project-detail">
|
||||
<h5 class="mt-0">{{'SUMMARY.PROJECT_QUOTAS' | translate}}</h5>
|
||||
<div class="ml-1">
|
||||
|
@ -2,12 +2,13 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { ProjectService, ErrorHandler} from '@harbor/ui';
|
||||
import { ProjectService, ErrorHandler, UserPermissionService } from '@harbor/ui';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { of } from 'rxjs';
|
||||
import { AppConfigService } from "../../app-config.service";
|
||||
import { SummaryComponent } from './summary.component';
|
||||
|
||||
|
||||
describe('SummaryComponent', () => {
|
||||
let component: SummaryComponent;
|
||||
let fixture: ComponentFixture<SummaryComponent>;
|
||||
@ -18,6 +19,11 @@ describe('SummaryComponent', () => {
|
||||
}
|
||||
};
|
||||
let fakeErrorHandler = null;
|
||||
let fakeUserPermissionService = {
|
||||
hasProjectPermissions: function() {
|
||||
return of([true, true]);
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -34,6 +40,7 @@ describe('SummaryComponent', () => {
|
||||
{ provide: AppConfigService, useValue: fakeAppConfigService },
|
||||
{ provide: ProjectService, useValue: fakeProjectService },
|
||||
{ provide: ErrorHandler, useValue: fakeErrorHandler },
|
||||
{ provide: UserPermissionService, useValue: fakeUserPermissionService },
|
||||
{
|
||||
provide: ActivatedRoute, useValue: {
|
||||
paramMap: of({ get: (key) => 'value' }),
|
||||
|
@ -1,41 +1,70 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ProjectService, clone, QuotaUnits, getSuitableUnit, ErrorHandler, GetIntegerAndUnit
|
||||
, QUOTA_DANGER_COEFFICIENT, QUOTA_WARNING_COEFFICIENT } from '@harbor/ui';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import {
|
||||
ProjectService,
|
||||
clone,
|
||||
QuotaUnits,
|
||||
getSuitableUnit,
|
||||
ErrorHandler,
|
||||
GetIntegerAndUnit,
|
||||
UserPermissionService,
|
||||
USERSTATICPERMISSION,
|
||||
QUOTA_DANGER_COEFFICIENT,
|
||||
QUOTA_WARNING_COEFFICIENT
|
||||
} from '@harbor/ui';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { AppConfigService } from "../../app-config.service";
|
||||
|
||||
@Component({
|
||||
selector: 'summary',
|
||||
templateUrl: './summary.component.html',
|
||||
styleUrls: ['./summary.component.scss']
|
||||
})
|
||||
export class SummaryComponent implements OnInit {
|
||||
showProjectMemberInfo: boolean;
|
||||
showQuotaInfo: boolean;
|
||||
|
||||
projectId: number;
|
||||
summaryInformation: any;
|
||||
quotaDangerCoefficient: number = QUOTA_DANGER_COEFFICIENT;
|
||||
quotaWarningCoefficient: number = QUOTA_WARNING_COEFFICIENT;
|
||||
constructor(
|
||||
private projectService: ProjectService,
|
||||
private userPermissionService: UserPermissionService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private appConfigService: AppConfigService,
|
||||
private route: ActivatedRoute
|
||||
) { }
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.projectId = this.route.snapshot.parent.params['id'];
|
||||
|
||||
const permissions = [
|
||||
{ resource: USERSTATICPERMISSION.MEMBER.KEY, action: USERSTATICPERMISSION.MEMBER.VALUE.LIST },
|
||||
{ resource: USERSTATICPERMISSION.QUOTA.KEY, action: USERSTATICPERMISSION.QUOTA.VALUE.READ },
|
||||
];
|
||||
|
||||
this.userPermissionService.hasProjectPermissions(this.projectId, permissions).subscribe((results: Array<boolean>) => {
|
||||
this.showProjectMemberInfo = results[0];
|
||||
this.showQuotaInfo = results[1];
|
||||
});
|
||||
|
||||
this.projectService.getProjectSummary(this.projectId).subscribe(res => {
|
||||
this.summaryInformation = res;
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
getSuitableUnit(value) {
|
||||
const QuotaUnitsCopy = clone(QuotaUnits);
|
||||
return getSuitableUnit(value, QuotaUnitsCopy);
|
||||
}
|
||||
|
||||
getIntegerAndUnit(hardValue, usedValue) {
|
||||
return GetIntegerAndUnit(hardValue, clone(QuotaUnits), usedValue, clone(QuotaUnits));
|
||||
}
|
||||
|
||||
public get withHelmChart(): boolean {
|
||||
return this.appConfigService.getConfig().with_chartmuseum;
|
||||
}
|
||||
|
@ -6,8 +6,7 @@
|
||||
</div>
|
||||
<hbr-tag-detail (backEvt)="goBack($event)"
|
||||
[tagId]="tagId"
|
||||
[withClair]="withClair"
|
||||
[withAdmiral]="withAdmiral"
|
||||
[projectId]="projectId"
|
||||
[repositoryId]="repositoryId"></hbr-tag-detail>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
import { Injectable } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ErrorHandler, UserPermissionService, httpStatusCode, errorHandler } from '@harbor/ui';
|
||||
import { ErrorHandler, httpStatusCode, errorHandler } from '@harbor/ui';
|
||||
|
||||
import { AlertType } from '../../shared/shared.const';
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
@ -26,7 +26,6 @@ export class MessageHandlerService implements ErrorHandler {
|
||||
constructor(
|
||||
private msgService: MessageService,
|
||||
private translate: TranslateService,
|
||||
private userPermissionService: UserPermissionService,
|
||||
private session: SessionService) { }
|
||||
|
||||
// Handle the error and map it to the suitable message
|
||||
@ -46,7 +45,6 @@ export class MessageHandlerService implements ErrorHandler {
|
||||
this.msgService.announceAppLevelMessage(code, msg, AlertType.DANGER);
|
||||
// Session is invalid now, clare session cache
|
||||
this.session.clear();
|
||||
this.userPermissionService.clearPermissionCache();
|
||||
} else {
|
||||
this.msgService.announceMessage(code, msg, AlertType.DANGER);
|
||||
}
|
||||
|
@ -21,7 +21,8 @@ import {
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { ProjectService } from '@harbor/ui';
|
||||
import { CommonRoutes } from '@harbor/ui';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class MemberGuard implements CanActivate, CanActivateChild {
|
||||
@ -31,49 +32,39 @@ export class MemberGuard implements CanActivate, CanActivateChild {
|
||||
private router: Router) {}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
|
||||
let projectId = route.params['id'];
|
||||
const projectId = route.params['id'];
|
||||
this.sessionService.setProjectMembers([]);
|
||||
return new Observable((observer) => {
|
||||
let user = this.sessionService.getCurrentUser();
|
||||
if (user === null) {
|
||||
this.sessionService.retrieveUser()
|
||||
.subscribe(() => {
|
||||
this.checkMemberStatus(state.url, projectId).subscribe((res) => observer.next(res));
|
||||
}, error => {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
observer.next(false);
|
||||
});
|
||||
} else {
|
||||
this.checkMemberStatus(state.url, projectId).subscribe((res) => observer.next(res));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkMemberStatus(url: string, projectId: number): Observable<boolean> {
|
||||
return new Observable<boolean>((observer) => {
|
||||
this.projectService.checkProjectMember(projectId)
|
||||
.subscribe(res => {
|
||||
this.sessionService.setProjectMembers(res);
|
||||
return observer.next(true);
|
||||
},
|
||||
const user = this.sessionService.getCurrentUser();
|
||||
if (user !== null) {
|
||||
return this.hasProjectPerm(state.url, projectId);
|
||||
}
|
||||
|
||||
return this.sessionService.retrieveUser().pipe(
|
||||
() => {
|
||||
// Add exception for repository in project detail router activation.
|
||||
this.projectService.getProject(projectId).subscribe(project => {
|
||||
if (project.metadata && project.metadata.public === 'true') {
|
||||
return observer.next(true);
|
||||
}
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return observer.next(false);
|
||||
},
|
||||
() => {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return observer.next(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
return this.hasProjectPerm(state.url, projectId);
|
||||
},
|
||||
catchError(err => {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return of(false);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
|
||||
return this.canActivate(route, state);
|
||||
}
|
||||
|
||||
hasProjectPerm(url: string, projectId: number): Observable<boolean> {
|
||||
// Note: current user will have the permission to visit the project when the user can get response from GET /projects/:id API.
|
||||
return this.projectService.getProject(projectId).pipe(
|
||||
map(() => {
|
||||
return true;
|
||||
}),
|
||||
catchError(err => {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return of(false);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -19,12 +19,12 @@ import {
|
||||
CanActivateChild
|
||||
} from '@angular/router';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { CommonRoutes, UserPermissionService } from '@harbor/ui';
|
||||
import { CommonRoutes } from '@harbor/ui';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class SignInGuard implements CanActivate, CanActivateChild {
|
||||
constructor(private authService: SessionService, private router: Router, private userPermission: UserPermissionService) { }
|
||||
constructor(private authService: SessionService, private router: Router) { }
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
|
||||
// If user has logged in, should not login again
|
||||
@ -35,7 +35,6 @@ export class SignInGuard implements CanActivate, CanActivateChild {
|
||||
this.authService.signOff()
|
||||
.subscribe(() => {
|
||||
this.authService.clear(); // Destroy session cache
|
||||
this.userPermission.clearPermissionCache();
|
||||
|
||||
return observer.next(true);
|
||||
}, error => {
|
||||
|
@ -21,7 +21,7 @@ import { Member } from '../project/member/member';
|
||||
|
||||
import { SignInCredential } from './sign-in-credential';
|
||||
import { enLang } from '../shared/shared.const';
|
||||
import { HTTP_FORM_OPTIONS, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from "@harbor/ui";
|
||||
import { HTTP_FORM_OPTIONS, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS, FlushAll } from "@harbor/ui";
|
||||
|
||||
const signInUrl = '/c/login';
|
||||
const currentUserEndpoint = "/api/users/current";
|
||||
@ -62,6 +62,7 @@ export class SessionService {
|
||||
clear(): void {
|
||||
this.currentUser = null;
|
||||
this.projectMembers = [];
|
||||
FlushAll();
|
||||
}
|
||||
|
||||
// Submit signin form to backend (NOT restful service)
|
||||
|
@ -60,14 +60,29 @@ export const enum ConfirmationButtons {
|
||||
}
|
||||
|
||||
export const ProjectTypes = { 0: 'PROJECT.ALL_PROJECTS', 1: 'PROJECT.PRIVATE_PROJECTS', 2: 'PROJECT.PUBLIC_PROJECTS' };
|
||||
export const RoleInfo = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST', 4: 'MEMBER.PROJECT_MASTER' };
|
||||
export const RoleMapping = { 'projectAdmin': 'MEMBER.PROJECT_ADMIN',
|
||||
'master': 'MEMBER.PROJECT_MASTER', 'developer': 'MEMBER.DEVELOPER', 'guest': 'MEMBER.GUEST' };
|
||||
|
||||
export const RoleInfo = {
|
||||
1: "MEMBER.PROJECT_ADMIN",
|
||||
2: "MEMBER.DEVELOPER",
|
||||
3: "MEMBER.GUEST",
|
||||
4: "MEMBER.PROJECT_MASTER",
|
||||
5: "MEMBER.LIMITED_GUEST",
|
||||
};
|
||||
|
||||
export const RoleMapping = {
|
||||
"projectAdmin": "MEMBER.PROJECT_ADMIN",
|
||||
"master": "MEMBER.PROJECT_MASTER",
|
||||
"developer": "MEMBER.DEVELOPER",
|
||||
"guest": "MEMBER.GUEST",
|
||||
"limitedGuest": "MEMBER.LIMITED_GUEST",
|
||||
};
|
||||
|
||||
export const ProjectRoles = [
|
||||
{ id: 1, value: "MEMBER.PROJECT_ADMIN" },
|
||||
{ id: 2, value: "MEMBER.DEVELOPER" },
|
||||
{ id: 3, value: "MEMBER.GUEST" },
|
||||
{ id: 4, value: "MEMBER.PROJECT_MASTER" },
|
||||
{ id: 5, value: "MEMBER.LIMITED_GUEST" },
|
||||
];
|
||||
|
||||
export enum Roles {
|
||||
@ -75,6 +90,7 @@ export enum Roles {
|
||||
PROJECT_MASTER = 4,
|
||||
DEVELOPER = 2,
|
||||
GUEST = 3,
|
||||
LIMITED_GUEST = 5,
|
||||
OTHER = 0,
|
||||
}
|
||||
export const DefaultHelmIcon = '/images/helm-gray.svg';
|
||||
|
@ -103,7 +103,7 @@ export const maintainUrlQueryParmas = function (uri: string, key: string, value:
|
||||
str += contentArray[1][getRandomInt(contentArray[1].length)];
|
||||
}
|
||||
if (!str.match(/[A-Z]+/g)) {
|
||||
str += contentArray[1][getRandomInt(contentArray[1].length)];
|
||||
str += contentArray[2][getRandomInt(contentArray[2].length)];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
@ -275,6 +275,7 @@
|
||||
"PROJECT_MASTER": "Master",
|
||||
"DEVELOPER": "Developer",
|
||||
"GUEST": "Guest",
|
||||
"LIMITED_GUEST": "Limited Guest",
|
||||
"DELETE": "Delete",
|
||||
"ITEMS": "items",
|
||||
"ACTIONS": "Actions",
|
||||
@ -621,6 +622,7 @@
|
||||
"PULL_COMMAND": "Pull Command",
|
||||
"PULL_TIME": "Pull Time",
|
||||
"PUSH_TIME": "Push Time",
|
||||
"IMMUTABLE": "Immutable",
|
||||
"MY_REPOSITORY": "My Repository",
|
||||
"PUBLIC_REPOSITORY": "Public Repository",
|
||||
"DELETION_TITLE_REPO": "Confirm Repository Deletion",
|
||||
@ -737,7 +739,8 @@
|
||||
"ADMIN": "Admin(s)",
|
||||
"MASTER": "Master(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)"
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet. Do you want to cancel?"
|
||||
@ -1258,10 +1261,10 @@
|
||||
"ADD_SCANNER": "Add Scanner",
|
||||
"EDIT_SCANNER": "Edit Scanner",
|
||||
"TEST_CONNECTION": "TEST CONNECTION",
|
||||
"ADD_SUCCESS": "Added successfully",
|
||||
"ADD_SUCCESS": "Successfully added ",
|
||||
"TEST_PASS": "Test passed",
|
||||
"TEST_FAILED": "Test failed",
|
||||
"UPDATE_SUCCESS": "Updated successfully",
|
||||
"UPDATE_SUCCESS": "Successfully updated",
|
||||
"SCANNER_COLON": "Scanner:",
|
||||
"NAME_COLON": "Name:",
|
||||
"VENDOR_COLON": "Vendor:",
|
||||
@ -1285,6 +1288,10 @@
|
||||
"ADAPTER": "Adapter",
|
||||
"VENDOR": "Vendor",
|
||||
"VERSION": "Version",
|
||||
"SELECT_SCANNER": "Select Scanner"
|
||||
"SELECT_SCANNER": "Select Scanner",
|
||||
"ENABLED": "Enabled",
|
||||
"ENABLE": "Enable",
|
||||
"DISABLE": "Disable",
|
||||
"DELETE_SUCCESS": "Successfully deleted"
|
||||
}
|
||||
}
|
||||
|
@ -276,6 +276,7 @@
|
||||
"PROJECT_MASTER": "Mantenedor",
|
||||
"DEVELOPER": "Desarrollador",
|
||||
"GUEST": "Invitado",
|
||||
"LIMITED_GUEST": "Limited Guest",
|
||||
"DELETE": "Eliminar",
|
||||
"ITEMS": "elementos",
|
||||
"ACTIONS": "Acciones",
|
||||
@ -622,6 +623,7 @@
|
||||
"PULL_COMMAND": "Comando Pull",
|
||||
"PULL_TIME": "Pull Time",
|
||||
"PUSH_TIME": "Push Time",
|
||||
"IMMUTABLE": "Immutable",
|
||||
"MY_REPOSITORY": "Mi Repositorio",
|
||||
"PUBLIC_REPOSITORY": "Repositorio Público",
|
||||
"DELETION_TITLE_REPO": "Confirmar Eliminación de Repositorio",
|
||||
@ -738,7 +740,8 @@
|
||||
"ADMIN": "Admin(s)",
|
||||
"MASTER": "Master(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)"
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Algunos cambios no se han guardado aún. ¿Quiere cancelar?"
|
||||
@ -1255,10 +1258,10 @@
|
||||
"ADD_SCANNER": "Add Scanner",
|
||||
"EDIT_SCANNER": "Edit Scanner",
|
||||
"TEST_CONNECTION": "TEST CONNECTION",
|
||||
"ADD_SUCCESS": "Added successfully",
|
||||
"ADD_SUCCESS": "Successfully added ",
|
||||
"TEST_PASS": "Test passed",
|
||||
"TEST_FAILED": "Test failed",
|
||||
"UPDATE_SUCCESS": "Updated successfully",
|
||||
"UPDATE_SUCCESS": "Successfully updated",
|
||||
"SCANNER_COLON": "Scanner:",
|
||||
"NAME_COLON": "Name:",
|
||||
"VENDOR_COLON": "Vendor:",
|
||||
@ -1282,6 +1285,10 @@
|
||||
"ADAPTER": "Adapter",
|
||||
"VENDOR": "Vendor",
|
||||
"VERSION": "Version",
|
||||
"SELECT_SCANNER": "Select Scanner"
|
||||
"SELECT_SCANNER": "Select Scanner",
|
||||
"ENABLED": "Enabled",
|
||||
"ENABLE": "Enable",
|
||||
"DISABLE": "Disable",
|
||||
"DELETE_SUCCESS": "Successfully deleted"
|
||||
}
|
||||
}
|
||||
|
@ -288,6 +288,7 @@
|
||||
"PROJECT_MASTER": "préposé à la maintenance",
|
||||
"DEVELOPER": "Développeur",
|
||||
"GUEST": "Invité",
|
||||
"LIMITED_GUEST": "Limited Guest",
|
||||
"DELETE": "Supprimer",
|
||||
"ITEMS": "items",
|
||||
"ACTIONS": "Actions",
|
||||
@ -611,6 +612,7 @@
|
||||
"PULL_COMMAND": "Commande de Pull",
|
||||
"PULL_TIME": "Pull Time",
|
||||
"PUSH_TIME": "Push Time",
|
||||
"IMMUTABLE": "Immutable",
|
||||
"MY_REPOSITORY": "Mon Dépôt",
|
||||
"PUBLIC_REPOSITORY": "Dépôt Public",
|
||||
"DELETION_TITLE_REPO": "Confirmer la Suppresion du Dépôt",
|
||||
@ -724,7 +726,8 @@
|
||||
"ADMIN": "Admin(s)",
|
||||
"MASTER": "Master(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)"
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Certaines modifications ne sont pas encore enregistrées. Voulez-vous annuler ?"
|
||||
@ -1227,10 +1230,10 @@
|
||||
"ADD_SCANNER": "Add Scanner",
|
||||
"EDIT_SCANNER": "Edit Scanner",
|
||||
"TEST_CONNECTION": "TEST CONNECTION",
|
||||
"ADD_SUCCESS": "Added successfully",
|
||||
"ADD_SUCCESS": "Successfully added ",
|
||||
"TEST_PASS": "Test passed",
|
||||
"TEST_FAILED": "Test failed",
|
||||
"UPDATE_SUCCESS": "Updated successfully",
|
||||
"UPDATE_SUCCESS": "Successfully updated",
|
||||
"SCANNER_COLON": "Scanner:",
|
||||
"NAME_COLON": "Name:",
|
||||
"VENDOR_COLON": "Vendor:",
|
||||
@ -1254,6 +1257,10 @@
|
||||
"ADAPTER": "Adapter",
|
||||
"VENDOR": "Vendor",
|
||||
"VERSION": "Version",
|
||||
"SELECT_SCANNER": "Select Scanner"
|
||||
"SELECT_SCANNER": "Select Scanner",
|
||||
"ENABLED": "Enabled",
|
||||
"ENABLE": "Enable",
|
||||
"DISABLE": "Disable",
|
||||
"DELETE_SUCCESS": "Successfully deleted"
|
||||
}
|
||||
}
|
||||
|
@ -273,6 +273,7 @@
|
||||
"PROJECT_MASTER": "Mantenedor",
|
||||
"DEVELOPER": "Desenvolvedor",
|
||||
"GUEST": "Visitante",
|
||||
"LIMITED_GUEST": "Limited Guest",
|
||||
"DELETE": "Remover",
|
||||
"ITEMS": "itens",
|
||||
"ACTIONS": "Ações",
|
||||
@ -621,6 +622,7 @@
|
||||
"PULL_COMMAND": "Comando de Pull",
|
||||
"PULL_TIME": "Pull Time",
|
||||
"PUSH_TIME": "Push Time",
|
||||
"IMMUTABLE": "Immutable",
|
||||
"MY_REPOSITORY": "Meu Repositório",
|
||||
"PUBLIC_REPOSITORY": "Repositório Público",
|
||||
"DELETION_TITLE_REPO": "Confirmar remoção de repositório",
|
||||
@ -733,7 +735,8 @@
|
||||
"ADMIN": "Admin(s)",
|
||||
"MASTER": "Master(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)"
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Algumas alterações ainda não foram salvas. Você deseja cancelar?"
|
||||
@ -1252,10 +1255,10 @@
|
||||
"ADD_SCANNER": "Add Scanner",
|
||||
"EDIT_SCANNER": "Edit Scanner",
|
||||
"TEST_CONNECTION": "TEST CONNECTION",
|
||||
"ADD_SUCCESS": "Added successfully",
|
||||
"ADD_SUCCESS": "Successfully added ",
|
||||
"TEST_PASS": "Test passed",
|
||||
"TEST_FAILED": "Test failed",
|
||||
"UPDATE_SUCCESS": "Updated successfully",
|
||||
"UPDATE_SUCCESS": "Successfully updated",
|
||||
"SCANNER_COLON": "Scanner:",
|
||||
"NAME_COLON": "Name:",
|
||||
"VENDOR_COLON": "Vendor:",
|
||||
@ -1279,7 +1282,11 @@
|
||||
"ADAPTER": "Adapter",
|
||||
"VENDOR": "Vendor",
|
||||
"VERSION": "Version",
|
||||
"SELECT_SCANNER": "Select Scanner"
|
||||
"SELECT_SCANNER": "Select Scanner",
|
||||
"ENABLED": "Enabled",
|
||||
"ENABLE": "Enable",
|
||||
"DISABLE": "Disable",
|
||||
"DELETE_SUCCESS": "Successfully deleted"
|
||||
}
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user