mirror of https://github.com/portainer/portainer
commit
02362defde
|
@ -1,5 +1,42 @@
|
|||
---
|
||||
engines:
|
||||
version: "2"
|
||||
checks:
|
||||
argument-count:
|
||||
enabled: true
|
||||
config:
|
||||
threshold: 4
|
||||
complex-logic:
|
||||
enabled: true
|
||||
config:
|
||||
threshold: 4
|
||||
file-lines:
|
||||
enabled: true
|
||||
config:
|
||||
threshold: 300
|
||||
method-complexity:
|
||||
enabled: false
|
||||
method-count:
|
||||
enabled: true
|
||||
config:
|
||||
threshold: 20
|
||||
method-lines:
|
||||
enabled: true
|
||||
config:
|
||||
threshold: 50
|
||||
nested-control-flow:
|
||||
enabled: true
|
||||
config:
|
||||
threshold: 4
|
||||
return-statements:
|
||||
enabled: false
|
||||
similar-code:
|
||||
enabled: true
|
||||
config:
|
||||
threshold: #language-specific defaults. overrides affect all languages.
|
||||
identical-code:
|
||||
enabled: true
|
||||
config:
|
||||
threshold: #language-specific defaults. overrides affect all languages.
|
||||
plugins:
|
||||
gofmt:
|
||||
enabled: true
|
||||
golint:
|
||||
|
@ -20,10 +57,5 @@ engines:
|
|||
config: .eslintrc.yml
|
||||
fixme:
|
||||
enabled: true
|
||||
ratings:
|
||||
paths:
|
||||
- "**.css"
|
||||
- "**.js"
|
||||
- "**.go"
|
||||
exclude_paths:
|
||||
exclude_patterns:
|
||||
- test/
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
version: '1.0'
|
||||
steps:
|
||||
|
||||
build_backend:
|
||||
image: portainer/golang-builder:ci
|
||||
working_directory: ${{main_clone}}
|
||||
commands:
|
||||
- mkdir -p /go/src/github.com/${{CF_REPO_OWNER}}
|
||||
- ln -s /codefresh/volume/${{CF_REPO_NAME}}/api /go/src/github.com/${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}
|
||||
- /build.sh api/cmd/portainer
|
||||
|
||||
build_frontend:
|
||||
image: portainer/angular-builder:latest
|
||||
working_directory: ${{build_backend}}
|
||||
commands:
|
||||
- yarn
|
||||
- yarn grunt build-webapp
|
||||
- mv api/cmd/portainer/portainer dist/
|
||||
|
||||
get_docker_version:
|
||||
image: alpine:3.7
|
||||
working_directory: ${{build_frontend}}
|
||||
commands:
|
||||
- cf_export DOCKER_VERSION=`cat gruntfile.js | grep -m 1 'shippedDockerVersion' | cut -d\' -f2`
|
||||
|
||||
download_docker_binary:
|
||||
image: busybox
|
||||
working_directory: ${{build_frontend}}
|
||||
commands:
|
||||
- echo ${{DOCKER_VERSION}}
|
||||
- wget -O /tmp/docker-binaries.tgz https://download.docker.com/linux/static/stable/x86_64/docker-${{DOCKER_VERSION}}.tgz
|
||||
- tar -xf /tmp/docker-binaries.tgz -C /tmp
|
||||
- mv /tmp/docker/docker dist/
|
||||
|
||||
build_image:
|
||||
type: build
|
||||
working_directory: ${{download_docker_binary}}
|
||||
dockerfile: ./build/linux/Dockerfile
|
||||
image_name: portainer/portainer
|
||||
tag: ${{CF_BRANCH}}
|
||||
|
||||
push_image:
|
||||
type: push
|
||||
candidate: '${{build_image}}'
|
||||
tag: '${{CF_BRANCH}}'
|
||||
registry: dockerhub
|
|
@ -1,46 +0,0 @@
|
|||
version: '1.0'
|
||||
steps:
|
||||
|
||||
build_backend:
|
||||
image: portainer/golang-builder:ci
|
||||
working_directory: ${{main_clone}}
|
||||
commands:
|
||||
- mkdir -p /go/src/github.com/${{CF_REPO_OWNER}}
|
||||
- ln -s /codefresh/volume/${{CF_REPO_NAME}}/api /go/src/github.com/${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}
|
||||
- /build.sh api/cmd/portainer
|
||||
|
||||
build_frontend:
|
||||
image: portainer/angular-builder:latest
|
||||
working_directory: ${{build_backend}}
|
||||
commands:
|
||||
- yarn
|
||||
- yarn grunt build-webapp
|
||||
- mv api/cmd/portainer/portainer dist/
|
||||
|
||||
get_docker_version:
|
||||
image: alpine:3.7
|
||||
working_directory: ${{build_frontend}}
|
||||
commands:
|
||||
- cf_export DOCKER_VERSION=`cat gruntfile.js | grep -m 1 'shippedDockerVersion' | cut -d\' -f2`
|
||||
|
||||
download_docker_binary:
|
||||
image: busybox
|
||||
working_directory: ${{build_frontend}}
|
||||
commands:
|
||||
- echo ${{DOCKER_VERSION}}
|
||||
- wget -O /tmp/docker-binaries.tgz https://download.docker.com/linux/static/stable/x86_64/docker-${{DOCKER_VERSION}}.tgz
|
||||
- tar -xf /tmp/docker-binaries.tgz -C /tmp
|
||||
- mv /tmp/docker/docker dist/
|
||||
|
||||
build_image:
|
||||
type: build
|
||||
working_directory: ${{download_docker_binary}}
|
||||
dockerfile: ./build/linux/Dockerfile
|
||||
image_name: portainer/portainer
|
||||
tag: ${{CF_BRANCH}}
|
||||
|
||||
push_image:
|
||||
type: push
|
||||
candidate: '${{build_image}}'
|
||||
tag: 'pr${{CF_PULL_REQUEST_NUMBER}}'
|
||||
registry: dockerhub
|
|
@ -141,7 +141,10 @@ rules:
|
|||
no-undef-init: error
|
||||
no-undef: off
|
||||
no-undefined: off
|
||||
no-unused-vars: off
|
||||
no-unused-vars:
|
||||
- warn
|
||||
-
|
||||
vars: local
|
||||
no-use-before-define: off
|
||||
|
||||
# Node.js and CommonJS
|
||||
|
|
|
@ -4,3 +4,4 @@ dist
|
|||
portainer-checksum.txt
|
||||
api/cmd/portainer/portainer*
|
||||
.tmp
|
||||
.vscode
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Some basic conventions for contributing to this project.
|
||||
|
||||
### General
|
||||
## General
|
||||
|
||||
Please make sure that there aren't existing pull requests attempting to address the issue mentioned. Likewise, please check for issues related to update, as someone else may be working on the issue in a branch or fork.
|
||||
|
||||
|
@ -13,7 +13,7 @@ When creating a new branch, prefix it with the *type* of the change (see section
|
|||
|
||||
For example, if you work on a bugfix for the issue #361, you could name the branch `fix361-template-selection`.
|
||||
|
||||
### Issues open to contribution
|
||||
## Issues open to contribution
|
||||
|
||||
Want to contribute but don't know where to start?
|
||||
|
||||
|
@ -24,14 +24,14 @@ Some of the open issues are labeled with prefix `exp/`, this is used to mark the
|
|||
either AngularJS or Golang
|
||||
* **advanced**: a task that require a deep understanding of the project codebase
|
||||
|
||||
You can have a use Github filters to list these issues:
|
||||
You can use Github filters to list these issues:
|
||||
|
||||
* beginner labeled issues: https://github.com/portainer/portainer/labels/exp%2Fbeginner
|
||||
* intermediate labeled issues: https://github.com/portainer/portainer/labels/exp%2Fintermediate
|
||||
* advanced labeled issues: https://github.com/portainer/portainer/labels/exp%2Fadvanced
|
||||
|
||||
|
||||
### Commit Message Format
|
||||
## Commit Message Format
|
||||
|
||||
Each commit message should include a **type**, a **scope** and a **subject**:
|
||||
|
||||
|
@ -47,7 +47,7 @@ Lines should not exceed 100 characters. This allows the message to be easier to
|
|||
#269 style(dashboard): update dashboard with new layout
|
||||
```
|
||||
|
||||
#### Type
|
||||
### Type
|
||||
|
||||
Must be one of the following:
|
||||
|
||||
|
@ -61,16 +61,30 @@ Must be one of the following:
|
|||
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
|
||||
generation
|
||||
|
||||
#### Scope
|
||||
### Scope
|
||||
|
||||
The scope could be anything specifying place of the commit change. For example `networks`,
|
||||
`containers`, `images` etc...
|
||||
You can use the **area** label tag associated on the issue here (for `area/containers` use `containers` as a scope...)
|
||||
|
||||
#### Subject
|
||||
### Subject
|
||||
|
||||
The subject contains succinct description of the change:
|
||||
|
||||
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
* don't capitalize first letter
|
||||
* no dot (.) at the end
|
||||
|
||||
## Contribution process
|
||||
|
||||
Our contribution process is described below. Some of the steps can be visualized inside Github via specific `contrib/` labels, such as `contrib/func-review-in-progress` or `contrib/tech-review-approved`.
|
||||
|
||||
### Bug report
|
||||
|
||||

|
||||
|
||||
### Feature request
|
||||
|
||||
The feature request process is similar to the bug report process but has an extra functional validation before the technical validation.
|
||||
|
||||

|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
[](https://hub.docker.com/r/portainer/portainer/)
|
||||
[](http://microbadger.com/images/portainer/portainer "Image size")
|
||||
[](http://portainer.readthedocs.io/en/stable/?badge=stable)
|
||||
[]( https://g.codefresh.io/repositories/portainer/portainer/builds?filter=trigger:build;branch:develop;service:5922a08a3a1aab000116fcc6~portainer-ci)
|
||||
[](https://semaphoreci.com/portainer/portainer)
|
||||
[](https://codeclimate.com/github/portainer/portainer)
|
||||
[](https://portainer.io/slack/)
|
||||
[](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/portainer/portainer/bolt/template"
|
||||
"github.com/portainer/portainer/bolt/user"
|
||||
"github.com/portainer/portainer/bolt/version"
|
||||
"github.com/portainer/portainer/bolt/webhook"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -47,6 +48,7 @@ type Store struct {
|
|||
TemplateService *template.Service
|
||||
UserService *user.Service
|
||||
VersionService *version.Service
|
||||
WebhookService *webhook.Service
|
||||
}
|
||||
|
||||
// NewStore initializes a new Store and the associated services
|
||||
|
@ -232,5 +234,11 @@ func (store *Store) initServices() error {
|
|||
}
|
||||
store.VersionService = versionService
|
||||
|
||||
webhookService, err := webhook.NewService(store.db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.WebhookService = webhookService
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package migrator
|
||||
|
||||
func (m *Migrator) updateResourceControlsToDBVersion14() error {
|
||||
resourceControls, err := m.resourceControlService.ResourceControls()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, resourceControl := range resourceControls {
|
||||
if resourceControl.AdministratorsOnly == true {
|
||||
err = m.resourceControlService.DeleteResourceControl(resourceControl.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -178,5 +178,13 @@ func (m *Migrator) Migrate() error {
|
|||
}
|
||||
}
|
||||
|
||||
// Portainer 1.19.2
|
||||
if m.currentDBVersion < 14 {
|
||||
err := m.updateResourceControlsToDBVersion14()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
package webhook
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "webhooks"
|
||||
)
|
||||
|
||||
// Service represents a service for managing webhook data.
|
||||
type Service struct {
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(db *bolt.DB) (*Service, error) {
|
||||
err := internal.CreateBucket(db, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//Webhooks returns an array of all webhooks
|
||||
func (service *Service) Webhooks() ([]portainer.Webhook, error) {
|
||||
var webhooks = make([]portainer.Webhook, 0)
|
||||
|
||||
err := service.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var webhook portainer.Webhook
|
||||
err := internal.UnmarshalObject(v, &webhook)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
webhooks = append(webhooks, webhook)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return webhooks, err
|
||||
}
|
||||
|
||||
// Webhook returns a webhook by ID.
|
||||
func (service *Service) Webhook(ID portainer.WebhookID) (*portainer.Webhook, error) {
|
||||
var webhook portainer.Webhook
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.db, BucketName, identifier, &webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &webhook, nil
|
||||
}
|
||||
|
||||
// WebhookByResourceID returns a webhook by the ResourceID it is associated with.
|
||||
func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, error) {
|
||||
var webhook *portainer.Webhook
|
||||
|
||||
err := service.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
cursor := bucket.Cursor()
|
||||
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var w portainer.Webhook
|
||||
err := internal.UnmarshalObject(v, &w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if w.ResourceID == ID {
|
||||
webhook = &w
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if webhook == nil {
|
||||
return portainer.ErrObjectNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return webhook, err
|
||||
}
|
||||
|
||||
// WebhookByToken returns a webhook by the random token it is associated with.
|
||||
func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error) {
|
||||
var webhook *portainer.Webhook
|
||||
|
||||
err := service.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
cursor := bucket.Cursor()
|
||||
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var w portainer.Webhook
|
||||
err := internal.UnmarshalObject(v, &w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if w.Token == token {
|
||||
webhook = &w
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if webhook == nil {
|
||||
return portainer.ErrObjectNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return webhook, err
|
||||
}
|
||||
|
||||
// DeleteWebhook deletes a webhook.
|
||||
func (service *Service) DeleteWebhook(ID portainer.WebhookID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
||||
}
|
||||
|
||||
// CreateWebhook assign an ID to a new webhook and saves it.
|
||||
func (service *Service) CreateWebhook(webhook *portainer.Webhook) error {
|
||||
return service.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
id, _ := bucket.NextSequence()
|
||||
webhook.ID = portainer.WebhookID(id)
|
||||
|
||||
data, err := internal.MarshalObject(webhook)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(webhook.ID)), data)
|
||||
})
|
||||
}
|
|
@ -178,6 +178,10 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
|
|||
SnapshotInterval: *flags.SnapshotInterval,
|
||||
}
|
||||
|
||||
if *flags.Templates != "" {
|
||||
settings.TemplatesURL = *flags.Templates
|
||||
}
|
||||
|
||||
if *flags.Labels != nil {
|
||||
settings.BlackListedLabels = *flags.Labels
|
||||
} else {
|
||||
|
@ -193,6 +197,10 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
|
|||
}
|
||||
|
||||
func initTemplates(templateService portainer.TemplateService, fileService portainer.FileService, templateURL, templateFile string) error {
|
||||
if templateURL != "" {
|
||||
log.Printf("Portainer started with the --templates flag. Using external templates, template management will be disabled.")
|
||||
return nil
|
||||
}
|
||||
|
||||
existingTemplates, err := templateService.Templates()
|
||||
if err != nil {
|
||||
|
@ -204,32 +212,14 @@ func initTemplates(templateService portainer.TemplateService, fileService portai
|
|||
return nil
|
||||
}
|
||||
|
||||
var templatesJSON []byte
|
||||
if templateURL == "" {
|
||||
return loadTemplatesFromFile(fileService, templateService, templateFile)
|
||||
}
|
||||
|
||||
templatesJSON, err = client.Get(templateURL)
|
||||
if err != nil {
|
||||
log.Println("Unable to retrieve templates via HTTP")
|
||||
return err
|
||||
}
|
||||
|
||||
return unmarshalAndPersistTemplates(templateService, templatesJSON)
|
||||
}
|
||||
|
||||
func loadTemplatesFromFile(fileService portainer.FileService, templateService portainer.TemplateService, templateFile string) error {
|
||||
templatesJSON, err := fileService.GetFileContent(templateFile)
|
||||
if err != nil {
|
||||
log.Println("Unable to retrieve template via filesystem")
|
||||
log.Println("Unable to retrieve template definitions via filesystem")
|
||||
return err
|
||||
}
|
||||
return unmarshalAndPersistTemplates(templateService, templatesJSON)
|
||||
}
|
||||
|
||||
func unmarshalAndPersistTemplates(templateService portainer.TemplateService, templateData []byte) error {
|
||||
var templates []portainer.Template
|
||||
err := json.Unmarshal(templateData, &templates)
|
||||
err = json.Unmarshal(templatesJSON, &templates)
|
||||
if err != nil {
|
||||
log.Println("Unable to parse templates file. Please review your template definition file.")
|
||||
return err
|
||||
|
@ -241,6 +231,7 @@ func unmarshalAndPersistTemplates(templateService portainer.TemplateService, tem
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -514,6 +505,7 @@ func main() {
|
|||
StackService: store.StackService,
|
||||
TagService: store.TagService,
|
||||
TemplateService: store.TemplateService,
|
||||
WebhookService: store.WebhookService,
|
||||
SwarmStackManager: swarmStackManager,
|
||||
ComposeStackManager: composeStackManager,
|
||||
CryptoService: cryptoService,
|
||||
|
@ -527,6 +519,7 @@ func main() {
|
|||
SSL: *flags.SSL,
|
||||
SSLCert: *flags.SSLCert,
|
||||
SSLKey: *flags.SSLKey,
|
||||
DockerClientFactory: clientFactory,
|
||||
}
|
||||
|
||||
log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr)
|
||||
|
|
|
@ -3,7 +3,6 @@ package crypto
|
|||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
|
@ -97,9 +96,7 @@ func (service *ECDSAService) GenerateKeyPair() ([]byte, []byte, error) {
|
|||
// that hash.
|
||||
// It then encodes the generated signature in base64.
|
||||
func (service *ECDSAService) Sign(message string) (string, error) {
|
||||
digest := md5.New()
|
||||
digest.Write([]byte(message))
|
||||
hash := digest.Sum(nil)
|
||||
hash := HashFromBytes([]byte(message))
|
||||
|
||||
r := big.NewInt(0)
|
||||
s := big.NewInt(0)
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package crypto
|
||||
|
||||
import "crypto/md5"
|
||||
|
||||
// HashFromBytes returns the hash of the specified data
|
||||
func HashFromBytes(data []byte) []byte {
|
||||
digest := md5.New()
|
||||
digest.Write(data)
|
||||
return digest.Sum(nil)
|
||||
}
|
|
@ -30,6 +30,11 @@ func snapshot(cli *client.Client) (*portainer.Snapshot, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = snapshotNodes(snapshot, cli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = snapshotContainers(snapshot, cli)
|
||||
|
@ -64,6 +69,22 @@ func snapshotInfo(snapshot *portainer.Snapshot, cli *client.Client) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func snapshotNodes(snapshot *portainer.Snapshot, cli *client.Client) error {
|
||||
nodes, err := cli.NodeList(context.Background(), types.NodeListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var nanoCpus int64
|
||||
var totalMem int64
|
||||
for _, node := range nodes {
|
||||
nanoCpus += node.Description.Resources.NanoCPUs
|
||||
totalMem += node.Description.Resources.MemoryBytes
|
||||
}
|
||||
snapshot.TotalCPU = int(nanoCpus / 1e9)
|
||||
snapshot.TotalMemory = totalMem
|
||||
return nil
|
||||
}
|
||||
|
||||
func snapshotSwarmServices(snapshot *portainer.Snapshot, cli *client.Client) error {
|
||||
stacks := make(map[string]struct{})
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ func (snapshotter *Snapshotter) CreateSnapshot(endpoint *portainer.Endpoint) (*p
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
return snapshot(cli)
|
||||
}
|
||||
|
|
|
@ -93,3 +93,9 @@ type Error string
|
|||
|
||||
// Error returns the error message.
|
||||
func (e Error) Error() string { return string(e) }
|
||||
|
||||
// Webhook errors
|
||||
const (
|
||||
ErrWebhookAlreadyExists = Error("A webhook for this resource already exists")
|
||||
ErrUnsupportedWebhookType = Error("Webhooks for this resource are not currently supported")
|
||||
)
|
||||
|
|
|
@ -13,6 +13,10 @@ import (
|
|||
"github.com/portainer/portainer"
|
||||
)
|
||||
|
||||
const (
|
||||
errInvalidResponseStatus = portainer.Error("Invalid response status (expecting 200)")
|
||||
)
|
||||
|
||||
// HTTPClient represents a client to send HTTP requests.
|
||||
type HTTPClient struct {
|
||||
*http.Client
|
||||
|
@ -75,6 +79,10 @@ func Get(url string) ([]byte, error) {
|
|||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, errInvalidResponseStatus
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
package error
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// LoggerHandler defines a HTTP handler that includes a HandlerError return pointer
|
||||
LoggerHandler func(http.ResponseWriter, *http.Request) *HandlerError
|
||||
// HandlerError represents an error raised inside a HTTP handler
|
||||
HandlerError struct {
|
||||
StatusCode int
|
||||
Message string
|
||||
Err error
|
||||
}
|
||||
errorResponse struct {
|
||||
Err string `json:"err,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
func (handler LoggerHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
err := handler(rw, r)
|
||||
if err != nil {
|
||||
writeErrorResponse(rw, err)
|
||||
}
|
||||
}
|
||||
|
||||
func writeErrorResponse(rw http.ResponseWriter, err *HandlerError) {
|
||||
log.Printf("http error: %s (err=%s) (code=%d)\n", err.Message, err.Err, err.StatusCode)
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(err.StatusCode)
|
||||
json.NewEncoder(rw).Encode(&errorResponse{Err: err.Message})
|
||||
}
|
||||
|
||||
// WriteError is a convenience function that creates a new HandlerError before calling writeErrorResponse.
|
||||
// For use outside of the standard http handlers.
|
||||
func WriteError(rw http.ResponseWriter, code int, message string, err error) {
|
||||
writeErrorResponse(rw, &HandlerError{code, message, err})
|
||||
}
|
|
@ -6,10 +6,10 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type authenticatePayload struct {
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ package dockerhub
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
)
|
||||
|
||||
// GET request on /api/dockerhub
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type dockerhubUpdatePayload struct {
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type endpointGroupCreatePayload struct {
|
||||
|
|
|
@ -3,10 +3,10 @@ package endpointgroups
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
// DELETE request on /api/endpoint_groups/:id
|
||||
|
|
|
@ -3,10 +3,10 @@ package endpointgroups
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
// GET request on /api/endpoint_groups/:id
|
||||
|
|
|
@ -3,8 +3,8 @@ package endpointgroups
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ package endpointgroups
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type endpointGroupUpdatePayload struct {
|
||||
|
|
|
@ -3,10 +3,10 @@ package endpointgroups
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type endpointGroupUpdateAccessPayload struct {
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ package endpointproxy
|
|||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
|
|
@ -3,9 +3,9 @@ package endpointproxy
|
|||
import (
|
||||
"strconv"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
|
||||
"net/http"
|
||||
)
|
||||
|
|
|
@ -3,9 +3,9 @@ package endpointproxy
|
|||
import (
|
||||
"strconv"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
|
||||
"net/http"
|
||||
)
|
||||
|
|
|
@ -3,9 +3,9 @@ package endpointproxy
|
|||
import (
|
||||
"strconv"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
|
||||
"net/http"
|
||||
)
|
||||
|
|
|
@ -6,12 +6,12 @@ import (
|
|||
"runtime"
|
||||
"strconv"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/crypto"
|
||||
"github.com/portainer/portainer/http/client"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type endpointCreatePayload struct {
|
||||
|
@ -35,7 +35,7 @@ type endpointCreatePayload struct {
|
|||
func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
||||
name, err := request.RetrieveMultiPartFormValue(r, "Name", false)
|
||||
if err != nil {
|
||||
return portainer.Error("Invalid stack name")
|
||||
return portainer.Error("Invalid endpoint name")
|
||||
}
|
||||
payload.Name = name
|
||||
|
||||
|
@ -71,7 +71,7 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
|||
payload.TLSSkipClientVerify = skipTLSClientVerification
|
||||
|
||||
if !payload.TLSSkipVerify {
|
||||
caCert, err := request.RetrieveMultiPartFormFile(r, "TLSCACertFile")
|
||||
caCert, _, err := request.RetrieveMultiPartFormFile(r, "TLSCACertFile")
|
||||
if err != nil {
|
||||
return portainer.Error("Invalid CA certificate file. Ensure that the file is uploaded correctly")
|
||||
}
|
||||
|
@ -79,13 +79,13 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
|||
}
|
||||
|
||||
if !payload.TLSSkipClientVerify {
|
||||
cert, err := request.RetrieveMultiPartFormFile(r, "TLSCertFile")
|
||||
cert, _, err := request.RetrieveMultiPartFormFile(r, "TLSCertFile")
|
||||
if err != nil {
|
||||
return portainer.Error("Invalid certificate file. Ensure that the file is uploaded correctly")
|
||||
}
|
||||
payload.TLSCertFile = cert
|
||||
|
||||
key, err := request.RetrieveMultiPartFormFile(r, "TLSKeyFile")
|
||||
key, _, err := request.RetrieveMultiPartFormFile(r, "TLSKeyFile")
|
||||
if err != nil {
|
||||
return portainer.Error("Invalid key file. Ensure that the file is uploaded correctly")
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po
|
|||
endpoint := &portainer.Endpoint{
|
||||
ID: portainer.EndpointID(endpointID),
|
||||
Name: payload.Name,
|
||||
URL: payload.URL,
|
||||
URL: "https://management.azure.com",
|
||||
Type: portainer.AzureEnvironment,
|
||||
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
||||
PublicURL: payload.PublicURL,
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
// DELETE request on /api/endpoints/:id
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type endpointExtensionAddPayload struct {
|
||||
|
|
|
@ -3,10 +3,10 @@ package endpoints
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
// DELETE request on /api/endpoints/:id/extensions/:extensionType
|
||||
|
|
|
@ -3,10 +3,10 @@ package endpoints
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
// GET request on /api/endpoints/:id
|
||||
|
|
|
@ -3,8 +3,8 @@ package endpoints
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
@ -27,8 +27,9 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
|
|||
|
||||
filteredEndpoints := security.FilterEndpoints(endpoints, endpointGroups, securityContext)
|
||||
|
||||
for _, endpoint := range filteredEndpoints {
|
||||
hideFields(&endpoint)
|
||||
for idx := range filteredEndpoints {
|
||||
hideFields(&filteredEndpoints[idx])
|
||||
}
|
||||
|
||||
return response.JSON(w, filteredEndpoints)
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
// POST request on /api/endpoints/snapshot
|
||||
|
|
|
@ -4,11 +4,11 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/http/client"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type endpointUpdatePayload struct {
|
||||
|
|
|
@ -3,10 +3,10 @@ package endpoints
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type endpointUpdateAccessPayload struct {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package endpoints
|
||||
|
||||
import (
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
|
||||
|
|
|
@ -33,5 +33,9 @@ func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
} else {
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
}
|
||||
|
||||
w.Header().Add("X-Frame-Options", "DENY")
|
||||
w.Header().Add("X-XSS-Protection", "1; mode=block")
|
||||
w.Header().Add("X-Content-Type-Options", "nosniff")
|
||||
handler.Handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/portainer/portainer/http/handler/endpointproxy"
|
||||
"github.com/portainer/portainer/http/handler/endpoints"
|
||||
"github.com/portainer/portainer/http/handler/file"
|
||||
"github.com/portainer/portainer/http/handler/motd"
|
||||
"github.com/portainer/portainer/http/handler/registries"
|
||||
"github.com/portainer/portainer/http/handler/resourcecontrols"
|
||||
"github.com/portainer/portainer/http/handler/settings"
|
||||
|
@ -21,6 +22,7 @@ import (
|
|||
"github.com/portainer/portainer/http/handler/templates"
|
||||
"github.com/portainer/portainer/http/handler/upload"
|
||||
"github.com/portainer/portainer/http/handler/users"
|
||||
"github.com/portainer/portainer/http/handler/webhooks"
|
||||
"github.com/portainer/portainer/http/handler/websocket"
|
||||
)
|
||||
|
||||
|
@ -33,6 +35,7 @@ type Handler struct {
|
|||
EndpointHandler *endpoints.Handler
|
||||
EndpointProxyHandler *endpointproxy.Handler
|
||||
FileHandler *file.Handler
|
||||
MOTDHandler *motd.Handler
|
||||
RegistryHandler *registries.Handler
|
||||
ResourceControlHandler *resourcecontrols.Handler
|
||||
SettingsHandler *settings.Handler
|
||||
|
@ -45,6 +48,7 @@ type Handler struct {
|
|||
UploadHandler *upload.Handler
|
||||
UserHandler *users.Handler
|
||||
WebSocketHandler *websocket.Handler
|
||||
WebhookHandler *webhooks.Handler
|
||||
}
|
||||
|
||||
// ServeHTTP delegates a request to the appropriate subhandler.
|
||||
|
@ -67,6 +71,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
default:
|
||||
http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r)
|
||||
}
|
||||
case strings.HasPrefix(r.URL.Path, "/api/motd"):
|
||||
http.StripPrefix("/api", h.MOTDHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/registries"):
|
||||
http.StripPrefix("/api", h.RegistryHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/resource_controls"):
|
||||
|
@ -91,6 +97,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
http.StripPrefix("/api", h.TeamMembershipHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/websocket"):
|
||||
http.StripPrefix("/api", h.WebSocketHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/webhooks"):
|
||||
http.StripPrefix("/api", h.WebhookHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/"):
|
||||
h.FileHandler.ServeHTTP(w, r)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package motd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle MOTD operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
}
|
||||
|
||||
// NewHandler returns a new Handler
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/motd",
|
||||
bouncer.AuthenticatedAccess(http.HandlerFunc(h.motd))).Methods(http.MethodGet)
|
||||
|
||||
return h
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package motd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/crypto"
|
||||
"github.com/portainer/portainer/http/client"
|
||||
)
|
||||
|
||||
type motdResponse struct {
|
||||
Message string `json:"Message"`
|
||||
Hash []byte `json:"Hash"`
|
||||
}
|
||||
|
||||
func (handler *Handler) motd(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
motd, err := client.Get(portainer.MessageOfTheDayURL)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
hash := crypto.HashFromBytes(motd)
|
||||
response.JSON(w, &motdResponse{Message: string(motd), Hash: hash})
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package registries
|
||||
|
||||
import (
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
|
||||
"net/http"
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type registryCreatePayload struct {
|
||||
|
|
|
@ -3,10 +3,10 @@ package registries
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
// DELETE request on /api/registries/:id
|
||||
|
|
|
@ -3,10 +3,10 @@ package registries
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
// GET request on /api/registries/:id
|
||||
|
|
|
@ -3,8 +3,8 @@ package registries
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
@ -22,8 +22,9 @@ func (handler *Handler) registryList(w http.ResponseWriter, r *http.Request) *ht
|
|||
|
||||
filteredRegistries := security.FilterRegistries(registries, securityContext)
|
||||
|
||||
for _, registry := range filteredRegistries {
|
||||
hideFields(®istry)
|
||||
for idx := range filteredRegistries {
|
||||
hideFields(&filteredRegistries[idx])
|
||||
}
|
||||
return response.JSON(w, registries)
|
||||
|
||||
return response.JSON(w, filteredRegistries)
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type registryUpdatePayload struct {
|
||||
|
|
|
@ -3,10 +3,10 @@ package registries
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type registryUpdateAccessPayload struct {
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -4,20 +4,20 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
type resourceControlCreatePayload struct {
|
||||
ResourceID string
|
||||
Type string
|
||||
AdministratorsOnly bool
|
||||
Users []int
|
||||
Teams []int
|
||||
SubResourceIDs []string
|
||||
ResourceID string
|
||||
Type string
|
||||
Public bool
|
||||
Users []int
|
||||
Teams []int
|
||||
SubResourceIDs []string
|
||||
}
|
||||
|
||||
func (payload *resourceControlCreatePayload) Validate(r *http.Request) error {
|
||||
|
@ -29,8 +29,8 @@ func (payload *resourceControlCreatePayload) Validate(r *http.Request) error {
|
|||
return portainer.Error("Invalid type")
|
||||
}
|
||||
|
||||
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly {
|
||||
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly")
|
||||
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public {
|
||||
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -90,12 +90,12 @@ func (handler *Handler) resourceControlCreate(w http.ResponseWriter, r *http.Req
|
|||
}
|
||||
|
||||
resourceControl := portainer.ResourceControl{
|
||||
ResourceID: payload.ResourceID,
|
||||
SubResourceIDs: payload.SubResourceIDs,
|
||||
Type: resourceControlType,
|
||||
AdministratorsOnly: payload.AdministratorsOnly,
|
||||
UserAccesses: userAccesses,
|
||||
TeamAccesses: teamAccesses,
|
||||
ResourceID: payload.ResourceID,
|
||||
SubResourceIDs: payload.SubResourceIDs,
|
||||
Type: resourceControlType,
|
||||
Public: payload.Public,
|
||||
UserAccesses: userAccesses,
|
||||
TeamAccesses: teamAccesses,
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
|
|
|
@ -3,10 +3,10 @@ package resourcecontrols
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,22 +3,22 @@ package resourcecontrols
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
type resourceControlUpdatePayload struct {
|
||||
AdministratorsOnly bool
|
||||
Users []int
|
||||
Teams []int
|
||||
Public bool
|
||||
Users []int
|
||||
Teams []int
|
||||
}
|
||||
|
||||
func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error {
|
||||
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly {
|
||||
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly")
|
||||
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public {
|
||||
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ func (handler *Handler) resourceControlUpdate(w http.ResponseWriter, r *http.Req
|
|||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to update the resource control", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
resourceControl.AdministratorsOnly = payload.AdministratorsOnly
|
||||
resourceControl.Public = payload.Public
|
||||
|
||||
var userAccesses = make([]portainer.UserResourceAccess, 0)
|
||||
for _, v := range payload.Users {
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ package settings
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
)
|
||||
|
||||
// GET request on /api/settings
|
||||
|
|
|
@ -3,11 +3,11 @@ package settings
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/filesystem"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type settingsLDAPCheckPayload struct {
|
||||
|
|
|
@ -3,9 +3,9 @@ package settings
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type publicSettingsResponse struct {
|
||||
|
@ -13,6 +13,7 @@ type publicSettingsResponse struct {
|
|||
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
|
||||
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
|
||||
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
|
||||
ExternalTemplates bool `json:"ExternalTemplates"`
|
||||
}
|
||||
|
||||
// GET request on /api/settings/public
|
||||
|
@ -27,6 +28,11 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
|
|||
AuthenticationMethod: settings.AuthenticationMethod,
|
||||
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
|
||||
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
|
||||
ExternalTemplates: false,
|
||||
}
|
||||
|
||||
if settings.TemplatesURL != "" {
|
||||
publicSettings.ExternalTemplates = true
|
||||
}
|
||||
|
||||
return response.JSON(w, publicSettings)
|
||||
|
|
|
@ -4,11 +4,11 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/filesystem"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type settingsUpdatePayload struct {
|
||||
|
@ -19,6 +19,7 @@ type settingsUpdatePayload struct {
|
|||
AllowBindMountsForRegularUsers *bool
|
||||
AllowPrivilegedModeForRegularUsers *bool
|
||||
SnapshotInterval *string
|
||||
TemplatesURL *string
|
||||
}
|
||||
|
||||
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
||||
|
@ -28,6 +29,9 @@ func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
|||
if payload.LogoURL != nil && *payload.LogoURL != "" && !govalidator.IsURL(*payload.LogoURL) {
|
||||
return portainer.Error("Invalid logo URL. Must correspond to a valid URL format")
|
||||
}
|
||||
if payload.TemplatesURL != nil && *payload.TemplatesURL != "" && !govalidator.IsURL(*payload.TemplatesURL) {
|
||||
return portainer.Error("Invalid external templates URL. Must correspond to a valid URL format")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -52,6 +56,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
|
|||
settings.LogoURL = *payload.LogoURL
|
||||
}
|
||||
|
||||
if payload.TemplatesURL != nil {
|
||||
settings.TemplatesURL = *payload.TemplatesURL
|
||||
}
|
||||
|
||||
if payload.BlackListedLabels != nil {
|
||||
settings.BlackListedLabels = payload.BlackListedLabels
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/filesystem"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
@ -194,7 +194,7 @@ func (payload *composeStackFromFileUploadPayload) Validate(r *http.Request) erro
|
|||
}
|
||||
payload.Name = name
|
||||
|
||||
composeFileContent, err := request.RetrieveMultiPartFormFile(r, "file")
|
||||
composeFileContent, _, err := request.RetrieveMultiPartFormFile(r, "file")
|
||||
if err != nil {
|
||||
return portainer.Error("Invalid Compose file. Ensure that the Compose file is uploaded correctly")
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/filesystem"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
@ -211,7 +211,7 @@ func (payload *swarmStackFromFileUploadPayload) Validate(r *http.Request) error
|
|||
}
|
||||
payload.SwarmID = swarmID
|
||||
|
||||
composeFileContent, err := request.RetrieveMultiPartFormFile(r, "file")
|
||||
composeFileContent, _, err := request.RetrieveMultiPartFormFile(r, "file")
|
||||
if err != nil {
|
||||
return portainer.Error("Invalid Compose file. Ensure that the Compose file is uploaded correctly")
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package stacks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
)
|
||||
|
||||
func (handler *Handler) cleanUp(stack *portainer.Stack, doCleanUp *bool) error {
|
||||
|
@ -57,7 +58,7 @@ func (handler *Handler) stackCreate(w http.ResponseWriter, r *http.Request) *htt
|
|||
return handler.createComposeStack(w, r, method, endpoint)
|
||||
}
|
||||
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: type. Value must be one of: 1 (Swarm stack) or 2 (Compose stack)", request.ErrInvalidQueryParameter}
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: type. Value must be one of: 1 (Swarm stack) or 2 (Compose stack)", errors.New(request.ErrInvalidQueryParameter)}
|
||||
}
|
||||
|
||||
func (handler *Handler) createComposeStack(w http.ResponseWriter, r *http.Request, method string, endpoint *portainer.Endpoint) *httperror.HandlerError {
|
||||
|
@ -71,7 +72,7 @@ func (handler *Handler) createComposeStack(w http.ResponseWriter, r *http.Reques
|
|||
return handler.createComposeStackFromFileUpload(w, r, endpoint)
|
||||
}
|
||||
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", request.ErrInvalidQueryParameter}
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", errors.New(request.ErrInvalidQueryParameter)}
|
||||
}
|
||||
|
||||
func (handler *Handler) createSwarmStack(w http.ResponseWriter, r *http.Request, method string, endpoint *portainer.Endpoint) *httperror.HandlerError {
|
||||
|
@ -84,5 +85,5 @@ func (handler *Handler) createSwarmStack(w http.ResponseWriter, r *http.Request,
|
|||
return handler.createSwarmStackFromFileUpload(w, r, endpoint)
|
||||
}
|
||||
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", request.ErrInvalidQueryParameter}
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", errors.New(request.ErrInvalidQueryParameter)}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
@ -48,8 +48,8 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if resourceControl != nil {
|
||||
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
if !securityContext.IsAdmin {
|
||||
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ import (
|
|||
"net/http"
|
||||
"path"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
@ -41,6 +41,10 @@ func (handler *Handler) stackFile(w http.ResponseWriter, r *http.Request) *httpe
|
|||
}
|
||||
|
||||
extendedStack := proxy.ExtendedStack{*stack, portainer.ResourceControl{}}
|
||||
if !securityContext.IsAdmin && resourceControl == nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
if resourceControl != nil {
|
||||
if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
extendedStack.ResourceControl = *resourceControl
|
||||
|
|
|
@ -3,11 +3,11 @@ package stacks
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
@ -36,6 +36,10 @@ func (handler *Handler) stackInspect(w http.ResponseWriter, r *http.Request) *ht
|
|||
}
|
||||
|
||||
extendedStack := proxy.ExtendedStack{*stack, portainer.ResourceControl{}}
|
||||
if !securityContext.IsAdmin && resourceControl == nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
if resourceControl != nil {
|
||||
if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
extendedStack.ResourceControl = *resourceControl
|
||||
|
|
|
@ -3,11 +3,11 @@ package stacks
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@ package stacks
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
@ -53,8 +53,8 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if resourceControl != nil {
|
||||
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
if !securityContext.IsAdmin {
|
||||
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,11 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
@ -62,8 +62,8 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if resourceControl != nil {
|
||||
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
if !securityContext.IsAdmin {
|
||||
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ package status
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
)
|
||||
|
||||
// GET request on /api/status
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type tagCreatePayload struct {
|
||||
|
|
|
@ -3,10 +3,10 @@ package tags
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
// DELETE request on /api/tags/:id
|
||||
|
|
|
@ -3,8 +3,8 @@ package tags
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
)
|
||||
|
||||
// GET request on /api/tags
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package teammemberships
|
||||
|
||||
import (
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
|
||||
"net/http"
|
||||
|
|
|
@ -3,10 +3,10 @@ package teammemberships
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ package teammemberships
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ package teammemberships
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ package teammemberships
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type teamCreatePayload struct {
|
||||
|
|
|
@ -3,10 +3,10 @@ package teams
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
// DELETE request on /api/teams/:id
|
||||
|
|
|
@ -3,10 +3,10 @@ package teams
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ package teams
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ package teams
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ package teams
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type teamUpdatePayload struct {
|
||||
|
|
|
@ -4,15 +4,20 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
const (
|
||||
errTemplateManagementDisabled = portainer.Error("Template management is disabled")
|
||||
)
|
||||
|
||||
// Handler represents an HTTP API handler for managing templates.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
TemplateService portainer.TemplateService
|
||||
SettingsService portainer.SettingsService
|
||||
}
|
||||
|
||||
// NewHandler returns a new instance of Handler.
|
||||
|
@ -20,15 +25,32 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
}
|
||||
|
||||
h.Handle("/templates",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.templateList))).Methods(http.MethodGet)
|
||||
h.Handle("/templates",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.templateCreate))).Methods(http.MethodPost)
|
||||
bouncer.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateCreate)))).Methods(http.MethodPost)
|
||||
h.Handle("/templates/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.templateInspect))).Methods(http.MethodGet)
|
||||
bouncer.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateInspect)))).Methods(http.MethodGet)
|
||||
h.Handle("/templates/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.templateUpdate))).Methods(http.MethodPut)
|
||||
bouncer.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateUpdate)))).Methods(http.MethodPut)
|
||||
h.Handle("/templates/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.templateDelete))).Methods(http.MethodDelete)
|
||||
bouncer.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateDelete)))).Methods(http.MethodDelete)
|
||||
return h
|
||||
}
|
||||
|
||||
func (handler *Handler) templateManagementCheck(next http.Handler) http.Handler {
|
||||
return httperror.LoggerHandler(func(rw http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
settings, err := handler.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||
}
|
||||
|
||||
if settings.TemplatesURL != "" {
|
||||
return &httperror.HandlerError{http.StatusServiceUnavailable, "Portainer is configured to use external templates, template management is disabled", errTemplateManagementDisabled}
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, r)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/filesystem"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type templateCreatePayload struct {
|
||||
|
|
|
@ -3,10 +3,10 @@ package templates
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
// DELETE request on /api/templates/:id
|
||||
|
|
|
@ -3,10 +3,10 @@ package templates
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
// GET request on /api/templates/:id
|
||||
|
|
|
@ -1,18 +1,40 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/http/client"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
// GET request on /api/templates
|
||||
func (handler *Handler) templateList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
templates, err := handler.TemplateService.Templates()
|
||||
settings, err := handler.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve templates from the database", err}
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||
}
|
||||
|
||||
var templates []portainer.Template
|
||||
if settings.TemplatesURL == "" {
|
||||
templates, err = handler.TemplateService.Templates()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve templates from the database", err}
|
||||
}
|
||||
} else {
|
||||
var templateData []byte
|
||||
templateData, err = client.Get(settings.TemplatesURL)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve external templates", err}
|
||||
}
|
||||
|
||||
err = json.Unmarshal(templateData, &templates)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to parse external templates", err}
|
||||
}
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
|
@ -21,6 +43,5 @@ func (handler *Handler) templateList(w http.ResponseWriter, r *http.Request) *ht
|
|||
}
|
||||
|
||||
filteredTemplates := security.FilterTemplates(templates, securityContext)
|
||||
|
||||
return response.JSON(w, filteredTemplates)
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@ package templates
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
)
|
||||
|
||||
type templateUpdatePayload struct {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package upload
|
||||
|
||||
import (
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
|
||||
"net/http"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue