Merge branch 'release/1.19.2'

pull/2276/head 1.19.2
Anthony Lapenna 2018-09-15 16:40:38 +08:00
commit 02362defde
281 changed files with 3507 additions and 1570 deletions

View File

@ -1,5 +1,42 @@
--- version: "2"
engines: 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: gofmt:
enabled: true enabled: true
golint: golint:
@ -20,10 +57,5 @@ engines:
config: .eslintrc.yml config: .eslintrc.yml
fixme: fixme:
enabled: true enabled: true
ratings: exclude_patterns:
paths:
- "**.css"
- "**.js"
- "**.go"
exclude_paths:
- test/ - test/

View File

@ -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

View File

@ -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

View File

@ -141,7 +141,10 @@ rules:
no-undef-init: error no-undef-init: error
no-undef: off no-undef: off
no-undefined: off no-undefined: off
no-unused-vars: off no-unused-vars:
- warn
-
vars: local
no-use-before-define: off no-use-before-define: off
# Node.js and CommonJS # Node.js and CommonJS

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ dist
portainer-checksum.txt portainer-checksum.txt
api/cmd/portainer/portainer* api/cmd/portainer/portainer*
.tmp .tmp
.vscode

View File

@ -2,7 +2,7 @@
Some basic conventions for contributing to this project. 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. 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`. 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? 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 either AngularJS or Golang
* **advanced**: a task that require a deep understanding of the project codebase * **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 * beginner labeled issues: https://github.com/portainer/portainer/labels/exp%2Fbeginner
* intermediate labeled issues: https://github.com/portainer/portainer/labels/exp%2Fintermediate * intermediate labeled issues: https://github.com/portainer/portainer/labels/exp%2Fintermediate
* advanced labeled issues: https://github.com/portainer/portainer/labels/exp%2Fadvanced * 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**: 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 #269 style(dashboard): update dashboard with new layout
``` ```
#### Type ### Type
Must be one of the following: 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 * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
generation generation
#### Scope ### Scope
The scope could be anything specifying place of the commit change. For example `networks`, The scope could be anything specifying place of the commit change. For example `networks`,
`containers`, `images` etc... `containers`, `images` etc...
You can use the **area** label tag associated on the issue here (for `area/containers` use `containers` as a scope...) 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: The subject contains succinct description of the change:
* use the imperative, present tense: "change" not "changed" nor "changes" * use the imperative, present tense: "change" not "changed" nor "changes"
* don't capitalize first letter * don't capitalize first letter
* no dot (.) at the end * 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
![portainer_bugreport_workflow](https://user-images.githubusercontent.com/5485061/43569306-5571b3a0-9637-11e8-8559-786cfc82a14f.png)
### Feature request
The feature request process is similar to the bug report process but has an extra functional validation before the technical validation.
![portainer_featurerequest_workflow](https://user-images.githubusercontent.com/5485061/43569315-5d30a308-9637-11e8-8292-3c62b5612925.png)

View File

@ -6,7 +6,7 @@
[![Docker Pulls](https://img.shields.io/docker/pulls/portainer/portainer.svg)](https://hub.docker.com/r/portainer/portainer/) [![Docker Pulls](https://img.shields.io/docker/pulls/portainer/portainer.svg)](https://hub.docker.com/r/portainer/portainer/)
[![Microbadger](https://images.microbadger.com/badges/image/portainer/portainer.svg)](http://microbadger.com/images/portainer/portainer "Image size") [![Microbadger](https://images.microbadger.com/badges/image/portainer/portainer.svg)](http://microbadger.com/images/portainer/portainer "Image size")
[![Documentation Status](https://readthedocs.org/projects/portainer/badge/?version=stable)](http://portainer.readthedocs.io/en/stable/?badge=stable) [![Documentation Status](https://readthedocs.org/projects/portainer/badge/?version=stable)](http://portainer.readthedocs.io/en/stable/?badge=stable)
[![Codefresh build status]( https://g.codefresh.io/api/badges/build?repoOwner=portainer&repoName=portainer&branch=develop&pipelineName=portainer-ci&accountName=deviantony&type=cf-1)]( https://g.codefresh.io/repositories/portainer/portainer/builds?filter=trigger:build;branch:develop;service:5922a08a3a1aab000116fcc6~portainer-ci) [![Build Status](https://semaphoreci.com/api/v1/portainer/portainer/branches/develop/badge.svg)](https://semaphoreci.com/portainer/portainer)
[![Code Climate](https://codeclimate.com/github/portainer/portainer/badges/gpa.svg)](https://codeclimate.com/github/portainer/portainer) [![Code Climate](https://codeclimate.com/github/portainer/portainer/badges/gpa.svg)](https://codeclimate.com/github/portainer/portainer)
[![Slack](https://portainer.io/slack/badge.svg)](https://portainer.io/slack/) [![Slack](https://portainer.io/slack/badge.svg)](https://portainer.io/slack/)
[![Gitter](https://badges.gitter.im/portainer/Lobby.svg)](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://badges.gitter.im/portainer/Lobby.svg)](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)

View File

@ -21,6 +21,7 @@ import (
"github.com/portainer/portainer/bolt/template" "github.com/portainer/portainer/bolt/template"
"github.com/portainer/portainer/bolt/user" "github.com/portainer/portainer/bolt/user"
"github.com/portainer/portainer/bolt/version" "github.com/portainer/portainer/bolt/version"
"github.com/portainer/portainer/bolt/webhook"
) )
const ( const (
@ -47,6 +48,7 @@ type Store struct {
TemplateService *template.Service TemplateService *template.Service
UserService *user.Service UserService *user.Service
VersionService *version.Service VersionService *version.Service
WebhookService *webhook.Service
} }
// NewStore initializes a new Store and the associated services // NewStore initializes a new Store and the associated services
@ -232,5 +234,11 @@ func (store *Store) initServices() error {
} }
store.VersionService = versionService store.VersionService = versionService
webhookService, err := webhook.NewService(store.db)
if err != nil {
return err
}
store.WebhookService = webhookService
return nil return nil
} }

View File

@ -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
}

View File

@ -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) return m.versionService.StoreDBVersion(portainer.DBVersion)
} }

151
api/bolt/webhook/webhook.go Normal file
View File

@ -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)
})
}

View File

@ -178,6 +178,10 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
SnapshotInterval: *flags.SnapshotInterval, SnapshotInterval: *flags.SnapshotInterval,
} }
if *flags.Templates != "" {
settings.TemplatesURL = *flags.Templates
}
if *flags.Labels != nil { if *flags.Labels != nil {
settings.BlackListedLabels = *flags.Labels settings.BlackListedLabels = *flags.Labels
} else { } 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 { 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() existingTemplates, err := templateService.Templates()
if err != nil { if err != nil {
@ -204,32 +212,14 @@ func initTemplates(templateService portainer.TemplateService, fileService portai
return nil 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) templatesJSON, err := fileService.GetFileContent(templateFile)
if err != nil { if err != nil {
log.Println("Unable to retrieve template via filesystem") log.Println("Unable to retrieve template definitions via filesystem")
return err return err
} }
return unmarshalAndPersistTemplates(templateService, templatesJSON)
}
func unmarshalAndPersistTemplates(templateService portainer.TemplateService, templateData []byte) error {
var templates []portainer.Template var templates []portainer.Template
err := json.Unmarshal(templateData, &templates) err = json.Unmarshal(templatesJSON, &templates)
if err != nil { if err != nil {
log.Println("Unable to parse templates file. Please review your template definition file.") log.Println("Unable to parse templates file. Please review your template definition file.")
return err return err
@ -241,6 +231,7 @@ func unmarshalAndPersistTemplates(templateService portainer.TemplateService, tem
return err return err
} }
} }
return nil return nil
} }
@ -514,6 +505,7 @@ func main() {
StackService: store.StackService, StackService: store.StackService,
TagService: store.TagService, TagService: store.TagService,
TemplateService: store.TemplateService, TemplateService: store.TemplateService,
WebhookService: store.WebhookService,
SwarmStackManager: swarmStackManager, SwarmStackManager: swarmStackManager,
ComposeStackManager: composeStackManager, ComposeStackManager: composeStackManager,
CryptoService: cryptoService, CryptoService: cryptoService,
@ -527,6 +519,7 @@ func main() {
SSL: *flags.SSL, SSL: *flags.SSL,
SSLCert: *flags.SSLCert, SSLCert: *flags.SSLCert,
SSLKey: *flags.SSLKey, SSLKey: *flags.SSLKey,
DockerClientFactory: clientFactory,
} }
log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr) log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr)

View File

@ -3,7 +3,6 @@ package crypto
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/md5"
"crypto/rand" "crypto/rand"
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
@ -97,9 +96,7 @@ func (service *ECDSAService) GenerateKeyPair() ([]byte, []byte, error) {
// that hash. // that hash.
// It then encodes the generated signature in base64. // It then encodes the generated signature in base64.
func (service *ECDSAService) Sign(message string) (string, error) { func (service *ECDSAService) Sign(message string) (string, error) {
digest := md5.New() hash := HashFromBytes([]byte(message))
digest.Write([]byte(message))
hash := digest.Sum(nil)
r := big.NewInt(0) r := big.NewInt(0)
s := big.NewInt(0) s := big.NewInt(0)

10
api/crypto/md5.go Normal file
View File

@ -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)
}

View File

@ -30,6 +30,11 @@ func snapshot(cli *client.Client) (*portainer.Snapshot, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = snapshotNodes(snapshot, cli)
if err != nil {
return nil, err
}
} }
err = snapshotContainers(snapshot, cli) err = snapshotContainers(snapshot, cli)
@ -64,6 +69,22 @@ func snapshotInfo(snapshot *portainer.Snapshot, cli *client.Client) error {
return nil 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 { func snapshotSwarmServices(snapshot *portainer.Snapshot, cli *client.Client) error {
stacks := make(map[string]struct{}) stacks := make(map[string]struct{})

View File

@ -22,6 +22,7 @@ func (snapshotter *Snapshotter) CreateSnapshot(endpoint *portainer.Endpoint) (*p
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer cli.Close()
return snapshot(cli) return snapshot(cli)
} }

View File

@ -93,3 +93,9 @@ type Error string
// Error returns the error message. // Error returns the error message.
func (e Error) Error() string { return string(e) } 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")
)

View File

@ -13,6 +13,10 @@ import (
"github.com/portainer/portainer" "github.com/portainer/portainer"
) )
const (
errInvalidResponseStatus = portainer.Error("Invalid response status (expecting 200)")
)
// HTTPClient represents a client to send HTTP requests. // HTTPClient represents a client to send HTTP requests.
type HTTPClient struct { type HTTPClient struct {
*http.Client *http.Client
@ -75,6 +79,10 @@ func Get(url string) ([]byte, error) {
} }
defer response.Body.Close() defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return nil, errInvalidResponseStatus
}
body, err := ioutil.ReadAll(response.Body) body, err := ioutil.ReadAll(response.Body)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -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})
}

View File

@ -6,10 +6,10 @@ import (
"strings" "strings"
"github.com/asaskevich/govalidator" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type authenticatePayload struct { type authenticatePayload struct {

View File

@ -4,8 +4,8 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,8 +3,8 @@ package dockerhub
import ( import (
"net/http" "net/http"
httperror "github.com/portainer/portainer/http/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/http/response" "github.com/portainer/libhttp/response"
) )
// GET request on /api/dockerhub // GET request on /api/dockerhub

View File

@ -4,10 +4,10 @@ import (
"net/http" "net/http"
"github.com/asaskevich/govalidator" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type dockerhubUpdatePayload struct { type dockerhubUpdatePayload struct {

View File

@ -4,8 +4,8 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -4,10 +4,10 @@ import (
"net/http" "net/http"
"github.com/asaskevich/govalidator" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type endpointGroupCreatePayload struct { type endpointGroupCreatePayload struct {

View File

@ -3,10 +3,10 @@ package endpointgroups
import ( import (
"net/http" "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"
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 // DELETE request on /api/endpoint_groups/:id

View File

@ -3,10 +3,10 @@ package endpointgroups
import ( import (
"net/http" "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"
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 // GET request on /api/endpoint_groups/:id

View File

@ -3,8 +3,8 @@ package endpointgroups
import ( import (
"net/http" "net/http"
httperror "github.com/portainer/portainer/http/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/http/response" "github.com/portainer/libhttp/response"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,10 +3,10 @@ package endpointgroups
import ( import (
"net/http" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type endpointGroupUpdatePayload struct { type endpointGroupUpdatePayload struct {

View File

@ -3,10 +3,10 @@ package endpointgroups
import ( import (
"net/http" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type endpointGroupUpdateAccessPayload struct { type endpointGroupUpdateAccessPayload struct {

View File

@ -4,8 +4,8 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -2,8 +2,8 @@ package endpointproxy
import ( import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy" "github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,9 +3,9 @@ package endpointproxy
import ( import (
"strconv" "strconv"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"net/http" "net/http"
) )

View File

@ -3,9 +3,9 @@ package endpointproxy
import ( import (
"strconv" "strconv"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"net/http" "net/http"
) )

View File

@ -3,9 +3,9 @@ package endpointproxy
import ( import (
"strconv" "strconv"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"net/http" "net/http"
) )

View File

@ -6,12 +6,12 @@ import (
"runtime" "runtime"
"strconv" "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"
"github.com/portainer/portainer/crypto" "github.com/portainer/portainer/crypto"
"github.com/portainer/portainer/http/client" "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 { type endpointCreatePayload struct {
@ -35,7 +35,7 @@ type endpointCreatePayload struct {
func (payload *endpointCreatePayload) Validate(r *http.Request) error { func (payload *endpointCreatePayload) Validate(r *http.Request) error {
name, err := request.RetrieveMultiPartFormValue(r, "Name", false) name, err := request.RetrieveMultiPartFormValue(r, "Name", false)
if err != nil { if err != nil {
return portainer.Error("Invalid stack name") return portainer.Error("Invalid endpoint name")
} }
payload.Name = name payload.Name = name
@ -71,7 +71,7 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
payload.TLSSkipClientVerify = skipTLSClientVerification payload.TLSSkipClientVerify = skipTLSClientVerification
if !payload.TLSSkipVerify { if !payload.TLSSkipVerify {
caCert, err := request.RetrieveMultiPartFormFile(r, "TLSCACertFile") caCert, _, err := request.RetrieveMultiPartFormFile(r, "TLSCACertFile")
if err != nil { if err != nil {
return portainer.Error("Invalid CA certificate file. Ensure that the file is uploaded correctly") 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 { if !payload.TLSSkipClientVerify {
cert, err := request.RetrieveMultiPartFormFile(r, "TLSCertFile") cert, _, err := request.RetrieveMultiPartFormFile(r, "TLSCertFile")
if err != nil { if err != nil {
return portainer.Error("Invalid certificate file. Ensure that the file is uploaded correctly") return portainer.Error("Invalid certificate file. Ensure that the file is uploaded correctly")
} }
payload.TLSCertFile = cert payload.TLSCertFile = cert
key, err := request.RetrieveMultiPartFormFile(r, "TLSKeyFile") key, _, err := request.RetrieveMultiPartFormFile(r, "TLSKeyFile")
if err != nil { if err != nil {
return portainer.Error("Invalid key file. Ensure that the file is uploaded correctly") 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{ endpoint := &portainer.Endpoint{
ID: portainer.EndpointID(endpointID), ID: portainer.EndpointID(endpointID),
Name: payload.Name, Name: payload.Name,
URL: payload.URL, URL: "https://management.azure.com",
Type: portainer.AzureEnvironment, Type: portainer.AzureEnvironment,
GroupID: portainer.EndpointGroupID(payload.GroupID), GroupID: portainer.EndpointGroupID(payload.GroupID),
PublicURL: payload.PublicURL, PublicURL: payload.PublicURL,

View File

@ -4,10 +4,10 @@ import (
"net/http" "net/http"
"strconv" "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"
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 // DELETE request on /api/endpoints/:id

View File

@ -4,10 +4,10 @@ import (
"net/http" "net/http"
"github.com/asaskevich/govalidator" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type endpointExtensionAddPayload struct { type endpointExtensionAddPayload struct {

View File

@ -3,10 +3,10 @@ package endpoints
import ( import (
"net/http" "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"
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 // DELETE request on /api/endpoints/:id/extensions/:extensionType

View File

@ -3,10 +3,10 @@ package endpoints
import ( import (
"net/http" "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"
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 // GET request on /api/endpoints/:id

View File

@ -3,8 +3,8 @@ package endpoints
import ( import (
"net/http" "net/http"
httperror "github.com/portainer/portainer/http/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/http/response" "github.com/portainer/libhttp/response"
"github.com/portainer/portainer/http/security" "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) filteredEndpoints := security.FilterEndpoints(endpoints, endpointGroups, securityContext)
for _, endpoint := range filteredEndpoints { for idx := range filteredEndpoints {
hideFields(&endpoint) hideFields(&filteredEndpoints[idx])
} }
return response.JSON(w, filteredEndpoints) return response.JSON(w, filteredEndpoints)
} }

View File

@ -4,9 +4,9 @@ import (
"log" "log"
"net/http" "net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
) )
// POST request on /api/endpoints/snapshot // POST request on /api/endpoints/snapshot

View File

@ -4,11 +4,11 @@ import (
"net/http" "net/http"
"strconv" "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"
"github.com/portainer/portainer/http/client" "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 { type endpointUpdatePayload struct {

View File

@ -3,10 +3,10 @@ package endpoints
import ( import (
"net/http" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type endpointUpdateAccessPayload struct { type endpointUpdateAccessPayload struct {

View File

@ -1,8 +1,8 @@
package endpoints package endpoints
import ( import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy" "github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"

View File

@ -33,5 +33,9 @@ func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} else { } else {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") 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) handler.Handler.ServeHTTP(w, r)
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/portainer/portainer/http/handler/endpointproxy" "github.com/portainer/portainer/http/handler/endpointproxy"
"github.com/portainer/portainer/http/handler/endpoints" "github.com/portainer/portainer/http/handler/endpoints"
"github.com/portainer/portainer/http/handler/file" "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/registries"
"github.com/portainer/portainer/http/handler/resourcecontrols" "github.com/portainer/portainer/http/handler/resourcecontrols"
"github.com/portainer/portainer/http/handler/settings" "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/templates"
"github.com/portainer/portainer/http/handler/upload" "github.com/portainer/portainer/http/handler/upload"
"github.com/portainer/portainer/http/handler/users" "github.com/portainer/portainer/http/handler/users"
"github.com/portainer/portainer/http/handler/webhooks"
"github.com/portainer/portainer/http/handler/websocket" "github.com/portainer/portainer/http/handler/websocket"
) )
@ -33,6 +35,7 @@ type Handler struct {
EndpointHandler *endpoints.Handler EndpointHandler *endpoints.Handler
EndpointProxyHandler *endpointproxy.Handler EndpointProxyHandler *endpointproxy.Handler
FileHandler *file.Handler FileHandler *file.Handler
MOTDHandler *motd.Handler
RegistryHandler *registries.Handler RegistryHandler *registries.Handler
ResourceControlHandler *resourcecontrols.Handler ResourceControlHandler *resourcecontrols.Handler
SettingsHandler *settings.Handler SettingsHandler *settings.Handler
@ -45,6 +48,7 @@ type Handler struct {
UploadHandler *upload.Handler UploadHandler *upload.Handler
UserHandler *users.Handler UserHandler *users.Handler
WebSocketHandler *websocket.Handler WebSocketHandler *websocket.Handler
WebhookHandler *webhooks.Handler
} }
// ServeHTTP delegates a request to the appropriate subhandler. // ServeHTTP delegates a request to the appropriate subhandler.
@ -67,6 +71,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
default: default:
http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r) 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"): case strings.HasPrefix(r.URL.Path, "/api/registries"):
http.StripPrefix("/api", h.RegistryHandler).ServeHTTP(w, r) http.StripPrefix("/api", h.RegistryHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/resource_controls"): 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) http.StripPrefix("/api", h.TeamMembershipHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/websocket"): case strings.HasPrefix(r.URL.Path, "/api/websocket"):
http.StripPrefix("/api", h.WebSocketHandler).ServeHTTP(w, r) 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, "/"): case strings.HasPrefix(r.URL.Path, "/"):
h.FileHandler.ServeHTTP(w, r) h.FileHandler.ServeHTTP(w, r)
} }

View File

@ -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
}

View File

@ -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})
}

View File

@ -1,8 +1,8 @@
package registries package registries
import ( import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
"net/http" "net/http"

View File

@ -4,10 +4,10 @@ import (
"net/http" "net/http"
"github.com/asaskevich/govalidator" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type registryCreatePayload struct { type registryCreatePayload struct {

View File

@ -3,10 +3,10 @@ package registries
import ( import (
"net/http" "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"
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 // DELETE request on /api/registries/:id

View File

@ -3,10 +3,10 @@ package registries
import ( import (
"net/http" "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"
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 // GET request on /api/registries/:id

View File

@ -3,8 +3,8 @@ package registries
import ( import (
"net/http" "net/http"
httperror "github.com/portainer/portainer/http/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/http/response" "github.com/portainer/libhttp/response"
"github.com/portainer/portainer/http/security" "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) filteredRegistries := security.FilterRegistries(registries, securityContext)
for _, registry := range filteredRegistries { for idx := range filteredRegistries {
hideFields(&registry) hideFields(&filteredRegistries[idx])
} }
return response.JSON(w, registries)
return response.JSON(w, filteredRegistries)
} }

View File

@ -4,10 +4,10 @@ import (
"net/http" "net/http"
"github.com/asaskevich/govalidator" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type registryUpdatePayload struct { type registryUpdatePayload struct {

View File

@ -3,10 +3,10 @@ package registries
import ( import (
"net/http" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type registryUpdateAccessPayload struct { type registryUpdateAccessPayload struct {

View File

@ -4,8 +4,8 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -4,20 +4,20 @@ import (
"net/http" "net/http"
"github.com/asaskevich/govalidator" "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"
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" "github.com/portainer/portainer/http/security"
) )
type resourceControlCreatePayload struct { type resourceControlCreatePayload struct {
ResourceID string ResourceID string
Type string Type string
AdministratorsOnly bool Public bool
Users []int Users []int
Teams []int Teams []int
SubResourceIDs []string SubResourceIDs []string
} }
func (payload *resourceControlCreatePayload) Validate(r *http.Request) error { 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") return portainer.Error("Invalid type")
} }
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly { if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public {
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly") return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public")
} }
return nil return nil
} }
@ -90,12 +90,12 @@ func (handler *Handler) resourceControlCreate(w http.ResponseWriter, r *http.Req
} }
resourceControl := portainer.ResourceControl{ resourceControl := portainer.ResourceControl{
ResourceID: payload.ResourceID, ResourceID: payload.ResourceID,
SubResourceIDs: payload.SubResourceIDs, SubResourceIDs: payload.SubResourceIDs,
Type: resourceControlType, Type: resourceControlType,
AdministratorsOnly: payload.AdministratorsOnly, Public: payload.Public,
UserAccesses: userAccesses, UserAccesses: userAccesses,
TeamAccesses: teamAccesses, TeamAccesses: teamAccesses,
} }
securityContext, err := security.RetrieveRestrictedRequestContext(r) securityContext, err := security.RetrieveRestrictedRequestContext(r)

View File

@ -3,10 +3,10 @@ package resourcecontrols
import ( import (
"net/http" "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"
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" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,22 +3,22 @@ package resourcecontrols
import ( import (
"net/http" "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"
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" "github.com/portainer/portainer/http/security"
) )
type resourceControlUpdatePayload struct { type resourceControlUpdatePayload struct {
AdministratorsOnly bool Public bool
Users []int Users []int
Teams []int Teams []int
} }
func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error { func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error {
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly { if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public {
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly") return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public")
} }
return nil 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} 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) var userAccesses = make([]portainer.UserResourceAccess, 0)
for _, v := range payload.Users { for _, v := range payload.Users {

View File

@ -4,8 +4,8 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,8 +3,8 @@ package settings
import ( import (
"net/http" "net/http"
httperror "github.com/portainer/portainer/http/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/http/response" "github.com/portainer/libhttp/response"
) )
// GET request on /api/settings // GET request on /api/settings

View File

@ -3,11 +3,11 @@ package settings
import ( import (
"net/http" "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"
"github.com/portainer/portainer/filesystem" "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 { type settingsLDAPCheckPayload struct {

View File

@ -3,9 +3,9 @@ package settings
import ( import (
"net/http" "net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
) )
type publicSettingsResponse struct { type publicSettingsResponse struct {
@ -13,6 +13,7 @@ type publicSettingsResponse struct {
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"` AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"` AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"` AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
ExternalTemplates bool `json:"ExternalTemplates"`
} }
// GET request on /api/settings/public // GET request on /api/settings/public
@ -27,6 +28,11 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
AuthenticationMethod: settings.AuthenticationMethod, AuthenticationMethod: settings.AuthenticationMethod,
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers, AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers, AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
ExternalTemplates: false,
}
if settings.TemplatesURL != "" {
publicSettings.ExternalTemplates = true
} }
return response.JSON(w, publicSettings) return response.JSON(w, publicSettings)

View File

@ -4,11 +4,11 @@ import (
"net/http" "net/http"
"github.com/asaskevich/govalidator" "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"
"github.com/portainer/portainer/filesystem" "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 { type settingsUpdatePayload struct {
@ -19,6 +19,7 @@ type settingsUpdatePayload struct {
AllowBindMountsForRegularUsers *bool AllowBindMountsForRegularUsers *bool
AllowPrivilegedModeForRegularUsers *bool AllowPrivilegedModeForRegularUsers *bool
SnapshotInterval *string SnapshotInterval *string
TemplatesURL *string
} }
func (payload *settingsUpdatePayload) Validate(r *http.Request) error { 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) { if payload.LogoURL != nil && *payload.LogoURL != "" && !govalidator.IsURL(*payload.LogoURL) {
return portainer.Error("Invalid logo URL. Must correspond to a valid URL format") 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 return nil
} }
@ -52,6 +56,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
settings.LogoURL = *payload.LogoURL settings.LogoURL = *payload.LogoURL
} }
if payload.TemplatesURL != nil {
settings.TemplatesURL = *payload.TemplatesURL
}
if payload.BlackListedLabels != nil { if payload.BlackListedLabels != nil {
settings.BlackListedLabels = payload.BlackListedLabels settings.BlackListedLabels = payload.BlackListedLabels
} }

View File

@ -6,11 +6,11 @@ import (
"strings" "strings"
"github.com/asaskevich/govalidator" "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"
"github.com/portainer/portainer/filesystem" "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" "github.com/portainer/portainer/http/security"
) )
@ -194,7 +194,7 @@ func (payload *composeStackFromFileUploadPayload) Validate(r *http.Request) erro
} }
payload.Name = name payload.Name = name
composeFileContent, err := request.RetrieveMultiPartFormFile(r, "file") composeFileContent, _, err := request.RetrieveMultiPartFormFile(r, "file")
if err != nil { if err != nil {
return portainer.Error("Invalid Compose file. Ensure that the Compose file is uploaded correctly") return portainer.Error("Invalid Compose file. Ensure that the Compose file is uploaded correctly")
} }

View File

@ -6,11 +6,11 @@ import (
"strings" "strings"
"github.com/asaskevich/govalidator" "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"
"github.com/portainer/portainer/filesystem" "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" "github.com/portainer/portainer/http/security"
) )
@ -211,7 +211,7 @@ func (payload *swarmStackFromFileUploadPayload) Validate(r *http.Request) error
} }
payload.SwarmID = swarmID payload.SwarmID = swarmID
composeFileContent, err := request.RetrieveMultiPartFormFile(r, "file") composeFileContent, _, err := request.RetrieveMultiPartFormFile(r, "file")
if err != nil { if err != nil {
return portainer.Error("Invalid Compose file. Ensure that the Compose file is uploaded correctly") return portainer.Error("Invalid Compose file. Ensure that the Compose file is uploaded correctly")
} }

View File

@ -5,8 +5,8 @@ import (
"sync" "sync"
"github.com/gorilla/mux" "github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -1,12 +1,13 @@
package stacks package stacks
import ( import (
"errors"
"log" "log"
"net/http" "net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/portainer" "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 { 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 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 { 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 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 { 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 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)}
} }

View File

@ -4,11 +4,11 @@ import (
"net/http" "net/http"
"strconv" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy" "github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security" "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} return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
} }
if resourceControl != nil { if !securityContext.IsAdmin {
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied} return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
} }
} }

View File

@ -4,11 +4,11 @@ import (
"net/http" "net/http"
"path" "path"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy" "github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security" "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{}} 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 resourceControl != nil {
if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
extendedStack.ResourceControl = *resourceControl extendedStack.ResourceControl = *resourceControl

View File

@ -3,11 +3,11 @@ package stacks
import ( import (
"net/http" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy" "github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security" "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{}} 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 resourceControl != nil {
if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
extendedStack.ResourceControl = *resourceControl extendedStack.ResourceControl = *resourceControl

View File

@ -3,11 +3,11 @@ package stacks
import ( import (
"net/http" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy" "github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,11 +3,11 @@ package stacks
import ( import (
"net/http" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy" "github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security" "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} return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
} }
if resourceControl != nil { if !securityContext.IsAdmin {
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied} return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
} }
} }

View File

@ -5,11 +5,11 @@ import (
"strconv" "strconv"
"github.com/asaskevich/govalidator" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy" "github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security" "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} return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
} }
if resourceControl != nil { if !securityContext.IsAdmin {
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied} return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
} }
} }

View File

@ -4,8 +4,8 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,8 +3,8 @@ package status
import ( import (
"net/http" "net/http"
httperror "github.com/portainer/portainer/http/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/http/response" "github.com/portainer/libhttp/response"
) )
// GET request on /api/status // GET request on /api/status

View File

@ -4,8 +4,8 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -4,10 +4,10 @@ import (
"net/http" "net/http"
"github.com/asaskevich/govalidator" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type tagCreatePayload struct { type tagCreatePayload struct {

View File

@ -3,10 +3,10 @@ package tags
import ( import (
"net/http" "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"
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 // DELETE request on /api/tags/:id

View File

@ -3,8 +3,8 @@ package tags
import ( import (
"net/http" "net/http"
httperror "github.com/portainer/portainer/http/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/http/response" "github.com/portainer/libhttp/response"
) )
// GET request on /api/tags // GET request on /api/tags

View File

@ -1,8 +1,8 @@
package teammemberships package teammemberships
import ( import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
"net/http" "net/http"

View File

@ -3,10 +3,10 @@ package teammemberships
import ( import (
"net/http" "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"
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" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,10 +3,10 @@ package teammemberships
import ( import (
"net/http" "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"
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" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,9 +3,9 @@ package teammemberships
import ( import (
"net/http" "net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,10 +3,10 @@ package teammemberships
import ( import (
"net/http" "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"
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" "github.com/portainer/portainer/http/security"
) )

View File

@ -4,8 +4,8 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -4,10 +4,10 @@ import (
"net/http" "net/http"
"github.com/asaskevich/govalidator" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type teamCreatePayload struct { type teamCreatePayload struct {

View File

@ -3,10 +3,10 @@ package teams
import ( import (
"net/http" "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"
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 // DELETE request on /api/teams/:id

View File

@ -3,10 +3,10 @@ package teams
import ( import (
"net/http" "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"
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" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,8 +3,8 @@ package teams
import ( import (
"net/http" "net/http"
httperror "github.com/portainer/portainer/http/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/http/response" "github.com/portainer/libhttp/response"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,10 +3,10 @@ package teams
import ( import (
"net/http" "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"
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" "github.com/portainer/portainer/http/security"
) )

View File

@ -3,10 +3,10 @@ package teams
import ( import (
"net/http" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type teamUpdatePayload struct { type teamUpdatePayload struct {

View File

@ -4,15 +4,20 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )
const (
errTemplateManagementDisabled = portainer.Error("Template management is disabled")
)
// Handler represents an HTTP API handler for managing templates. // Handler represents an HTTP API handler for managing templates.
type Handler struct { type Handler struct {
*mux.Router *mux.Router
TemplateService portainer.TemplateService TemplateService portainer.TemplateService
SettingsService portainer.SettingsService
} }
// NewHandler returns a new instance of Handler. // NewHandler returns a new instance of Handler.
@ -20,15 +25,32 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{ h := &Handler{
Router: mux.NewRouter(), Router: mux.NewRouter(),
} }
h.Handle("/templates", h.Handle("/templates",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.templateList))).Methods(http.MethodGet) bouncer.RestrictedAccess(httperror.LoggerHandler(h.templateList))).Methods(http.MethodGet)
h.Handle("/templates", 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}", 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}", 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}", 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 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
})
}

View File

@ -4,11 +4,11 @@ import (
"net/http" "net/http"
"github.com/asaskevich/govalidator" "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"
"github.com/portainer/portainer/filesystem" "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 { type templateCreatePayload struct {

View File

@ -3,10 +3,10 @@ package templates
import ( import (
"net/http" "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"
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 // DELETE request on /api/templates/:id

View File

@ -3,10 +3,10 @@ package templates
import ( import (
"net/http" "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"
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 // GET request on /api/templates/:id

View File

@ -1,18 +1,40 @@
package templates package templates
import ( import (
"encoding/json"
"net/http" "net/http"
httperror "github.com/portainer/portainer/http/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/http/response" "github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
"github.com/portainer/portainer/http/client"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
) )
// GET request on /api/templates // GET request on /api/templates
func (handler *Handler) templateList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { 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 { 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) 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) filteredTemplates := security.FilterTemplates(templates, securityContext)
return response.JSON(w, filteredTemplates) return response.JSON(w, filteredTemplates)
} }

View File

@ -3,10 +3,10 @@ package templates
import ( import (
"net/http" "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"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
) )
type templateUpdatePayload struct { type templateUpdatePayload struct {

View File

@ -1,8 +1,8 @@
package upload package upload
import ( import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer" "github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security" "github.com/portainer/portainer/http/security"
"net/http" "net/http"

Some files were not shown because too many files have changed in this diff Show More