rework user and admin profiles
users and admins can now also update their email and description
This commit is contained in:
parent
af8fa7ff81
commit
ba1febba73
25 changed files with 1038 additions and 798 deletions
14
.github/workflows/development.yml
vendored
14
.github/workflows/development.yml
vendored
|
@ -45,7 +45,7 @@ jobs:
|
|||
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/version.date=$DATE_TIME" -o sftpgo.exe
|
||||
|
||||
- name: Run test cases using SQLite provider
|
||||
run: go test -v -p 1 -timeout 10m ./... -coverprofile=coverage.txt -covermode=atomic
|
||||
run: go test -v -p 1 -timeout 15m ./... -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
if: ${{ matrix.upload-coverage }}
|
||||
|
@ -69,7 +69,7 @@ jobs:
|
|||
SFTPGO_DATA_PROVIDER__NAME: 'sftpgo_bolt.db'
|
||||
|
||||
- name: Run test cases using memory provider
|
||||
run: go test -v -p 1 -timeout 10m ./... -covermode=atomic
|
||||
run: go test -v -p 1 -timeout 15m ./... -covermode=atomic
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: memory
|
||||
SFTPGO_DATA_PROVIDER__NAME: ''
|
||||
|
@ -108,7 +108,7 @@ jobs:
|
|||
path: output
|
||||
|
||||
test-goarch-386:
|
||||
name: Run test cases on 32 bit arch
|
||||
name: Run test cases on 32-bit arch
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
@ -125,7 +125,7 @@ jobs:
|
|||
GOARCH: 386
|
||||
|
||||
- name: Run test cases
|
||||
run: go test -v -p 1 -timeout 10m ./... -covermode=atomic
|
||||
run: go test -v -p 1 -timeout 15m ./... -covermode=atomic
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: memory
|
||||
SFTPGO_DATA_PROVIDER__NAME: ''
|
||||
|
@ -177,7 +177,7 @@ jobs:
|
|||
|
||||
- name: Run tests using PostgreSQL provider
|
||||
run: |
|
||||
go test -v -p 1 -timeout 10m ./... -covermode=atomic
|
||||
go test -v -p 1 -timeout 15m ./... -covermode=atomic
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: postgresql
|
||||
SFTPGO_DATA_PROVIDER__NAME: sftpgo
|
||||
|
@ -188,7 +188,7 @@ jobs:
|
|||
|
||||
- name: Run tests using MySQL provider
|
||||
run: |
|
||||
go test -v -p 1 -timeout 10m ./... -covermode=atomic
|
||||
go test -v -p 1 -timeout 15m ./... -covermode=atomic
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: mysql
|
||||
SFTPGO_DATA_PROVIDER__NAME: sftpgo
|
||||
|
@ -201,7 +201,7 @@ jobs:
|
|||
run: |
|
||||
docker run --rm --name crdb --health-cmd "curl -I http://127.0.0.1:8080" --health-interval 10s --health-timeout 5s --health-retries 6 -p 26257:26257 -d cockroachdb/cockroach:latest start-single-node --insecure --listen-addr 0.0.0.0:26257
|
||||
docker exec crdb cockroach sql --insecure -e 'create database "sftpgo"'
|
||||
go test -v -p 1 -timeout 10m ./... -covermode=atomic
|
||||
go test -v -p 1 -timeout 15m ./... -covermode=atomic
|
||||
docker stop crdb
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: cockroachdb
|
||||
|
|
|
@ -736,6 +736,11 @@ func (u *User) CanChangeAPIKeyAuth() bool {
|
|||
return !util.IsStringInSlice(sdk.WebClientAPIKeyAuthChangeDisabled, u.Filters.WebClient)
|
||||
}
|
||||
|
||||
// CanChangeInfo returns true if this user is allowed to change its info such as email and description
|
||||
func (u *User) CanChangeInfo() bool {
|
||||
return !util.IsStringInSlice(sdk.WebClientInfoChangeDisabled, u.Filters.WebClient)
|
||||
}
|
||||
|
||||
// CanManagePublicKeys returns true if this user is allowed to manage public keys
|
||||
// from the web client. Used in web client UI
|
||||
func (u *User) CanManagePublicKeys() bool {
|
||||
|
|
|
@ -252,9 +252,9 @@ The configuration file contains the following sections:
|
|||
- `url`, string, optional. If not empty, the header will be added only if the request URL starts with the one specified here
|
||||
- **kms**, configuration for the Key Management Service, more details can be found [here](./kms.md)
|
||||
- `secrets`
|
||||
- `url`, string. Defines the URI to the KMS service. Default empty.
|
||||
- `master_key`, string. Defines the master encryption key as string. If not empty, it takes precedence over `master_key_path`. Default empty.
|
||||
- `master_key_path, string. Defines the absolute path to a file containing the master encryption key. Default empty.
|
||||
- `url`, string. Defines the URI to the KMS service. Default: empty.
|
||||
- `master_key`, string. Defines the master encryption key as string. If not empty, it takes precedence over `master_key_path`. Default: empty.
|
||||
- `master_key_path, string. Defines the absolute path to a file containing the master encryption key. Default: empty.
|
||||
- **mfa**, multi-factor authentication settings
|
||||
- `totp`, list of struct that define settings for time-based one time passwords (RFC 6238). Each struct has the following fields:
|
||||
- `name`, string. Unique configuration name. This name should not be changed if there are users or admins using the configuration. The name is not exposed to the authentication apps. Default: `Default`.
|
||||
|
|
16
go.mod
16
go.mod
|
@ -7,8 +7,8 @@ require (
|
|||
github.com/Azure/azure-storage-blob-go v0.14.0
|
||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
|
||||
github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8
|
||||
github.com/aws/aws-sdk-go v1.40.49
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.1.1
|
||||
github.com/aws/aws-sdk-go v1.40.51
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.0
|
||||
github.com/eikenb/pipeat v0.0.0-20210603033007-44fc3ffce52b
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fclairamb/ftpserverlib v0.16.0
|
||||
|
@ -43,7 +43,7 @@ require (
|
|||
github.com/pkg/sftp v1.13.4
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/prometheus/common v0.31.0 // indirect
|
||||
github.com/prometheus/common v0.31.1 // indirect
|
||||
github.com/rs/cors v1.8.0
|
||||
github.com/rs/xid v1.3.0
|
||||
github.com/rs/zerolog v1.25.0
|
||||
|
@ -62,17 +62,17 @@ require (
|
|||
gocloud.dev v0.24.0
|
||||
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272
|
||||
golang.org/x/net v0.0.0-20210924151903-3ad01bbaa167
|
||||
golang.org/x/sys v0.0.0-20210927052749-1cf2251ac284
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
|
||||
google.golang.org/api v0.57.0
|
||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0 // indirect
|
||||
google.golang.org/api v0.58.0
|
||||
google.golang.org/genproto v0.0.0-20210928142010-c7af6a1a74c9 // indirect
|
||||
google.golang.org/grpc v1.41.0
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.95.0 // indirect
|
||||
cloud.google.com/go v0.96.0 // indirect
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
|
@ -85,7 +85,7 @@ require (
|
|||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/go-ole/go-ole v1.2.5 // indirect
|
||||
github.com/goccy/go-json v0.7.8 // indirect
|
||||
github.com/goccy/go-json v0.7.9 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
|
|
34
go.sum
34
go.sum
|
@ -32,8 +32,8 @@ cloud.google.com/go v0.92.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y
|
|||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go v0.94.0/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
||||
cloud.google.com/go v0.95.0 h1:JVWssQIj9cLwHmLjqWLptFa83o7HgqUictM6eyvGWJE=
|
||||
cloud.google.com/go v0.95.0/go.mod h1:MzZUAH870Y7E+c14j23Ir66FC1+PK8WLG7OG4SjP+0k=
|
||||
cloud.google.com/go v0.96.0 h1:r9XIwQ9FrJspMjHulRm1kl1uanw5gSolzSK+dukeH0E=
|
||||
cloud.google.com/go v0.96.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
|
@ -136,8 +136,8 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
|
|||
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||
github.com/aws/aws-sdk-go v1.40.49 h1:kIbJYc4FZA2r4yxNU5giIR4HHLRkG9roFReWAsk0ZVQ=
|
||||
github.com/aws/aws-sdk-go v1.40.49/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||
github.com/aws/aws-sdk-go v1.40.51 h1:FfxDcjWqhMGwy+raf5Zf6AH8qsHIl9YG2dvJIBx1Aw4=
|
||||
github.com/aws/aws-sdk-go v1.40.51/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||
github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY=
|
||||
|
@ -184,8 +184,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
|
|||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.1.1 h1:3XzfSMuUT0wBe1a3o5C0eOTcArhmmFAg2Jzh/7hhKqo=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.0/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
|
@ -288,9 +288,12 @@ github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22
|
|||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/goccy/go-json v0.7.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.7.8 h1:CvMH7LotYymYuLGEohBM1lTZWX4g6jzWUUl2aLFuBoE=
|
||||
github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.7.9 h1:mSp3uo1tr6MXQTYopSNhHTUnJhd2zQ4Yk+HdJZP+ZRY=
|
||||
github.com/goccy/go-json v0.7.9/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
|
@ -681,8 +684,8 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
|
|||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.31.0 h1:FTJdLTjtrh4dXlCjpzdZJXMnejSTL5F/nVQm5sNwD34=
|
||||
github.com/prometheus/common v0.31.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.31.1 h1:d18hG4PkHnNAKNMOmFuXFaiY8Us0nird/2m60uS1AMs=
|
||||
github.com/prometheus/common v0.31.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
|
@ -954,8 +957,9 @@ golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927052749-1cf2251ac284 h1:lBPNCmq8u4zFP3huKCmUQ2Fx8kcY4X+O12UgGnyKsrg=
|
||||
golang.org/x/sys v0.0.0-20210927052749-1cf2251ac284/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1079,8 +1083,9 @@ google.golang.org/api v0.52.0/go.mod h1:Him/adpjt0sxtkWViy0b6xyKW/SD71CwdJ7HqJo7
|
|||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.57.0 h1:4t9zuDlHLcIx0ZEhmXEeFVCRsiOgpgn2QOH9N0MNjPI=
|
||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
||||
google.golang.org/api v0.58.0 h1:MDkAbYIB1JpSgCTOCYYoIec/coMlKK4oVbpnBLLcyT0=
|
||||
google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
@ -1152,9 +1157,10 @@ google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEc
|
|||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0 h1:5Tbluzus3QxoAJx4IefGt1W0HQZW4nuMrVk684jI74Q=
|
||||
google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20210928142010-c7af6a1a74c9 h1:XTH066D35LyHehRwlYhoK3qA+Hcgvg5xREG4kFQEW1Y=
|
||||
google.golang.org/genproto v0.0.0-20210928142010-c7af6a1a74c9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
|
|
|
@ -155,7 +155,7 @@ func deleteAdmin(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "Admin deleted", http.StatusOK)
|
||||
}
|
||||
|
||||
func getAdminAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) {
|
||||
func getAdminProfile(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
claims, err := getTokenClaims(r)
|
||||
if err != nil || claims.Username == "" {
|
||||
|
@ -167,13 +167,17 @@ func getAdminAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
resp := apiKeyAuth{
|
||||
AllowAPIKeyAuth: admin.Filters.AllowAPIKeyAuth,
|
||||
resp := adminProfile{
|
||||
baseProfile: baseProfile{
|
||||
Email: admin.Email,
|
||||
Description: admin.Description,
|
||||
AllowAPIKeyAuth: admin.Filters.AllowAPIKeyAuth,
|
||||
},
|
||||
}
|
||||
render.JSON(w, r, resp)
|
||||
}
|
||||
|
||||
func changeAdminAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) {
|
||||
func updateAdminProfile(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
claims, err := getTokenClaims(r)
|
||||
if err != nil || claims.Username == "" {
|
||||
|
@ -185,18 +189,20 @@ func changeAdminAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
var req apiKeyAuth
|
||||
var req adminProfile
|
||||
err = render.DecodeJSON(r.Body, &req)
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
admin.Email = req.Email
|
||||
admin.Description = req.Description
|
||||
admin.Filters.AllowAPIKeyAuth = req.AllowAPIKeyAuth
|
||||
if err := dataprovider.UpdateAdmin(&admin); err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
sendAPIResponse(w, r, err, "API key authentication status updated", http.StatusOK)
|
||||
sendAPIResponse(w, r, err, "Profile updated", http.StatusOK)
|
||||
}
|
||||
|
||||
func changeAdminPassword(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -349,7 +349,7 @@ func setUserPublicKeys(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "Public keys updated", http.StatusOK)
|
||||
}
|
||||
|
||||
func getUserAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) {
|
||||
func getUserProfile(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
claims, err := getTokenClaims(r)
|
||||
if err != nil || claims.Username == "" {
|
||||
|
@ -361,20 +361,25 @@ func getUserAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
resp := apiKeyAuth{
|
||||
AllowAPIKeyAuth: user.Filters.AllowAPIKeyAuth,
|
||||
resp := userProfile{
|
||||
baseProfile: baseProfile{
|
||||
Email: user.Email,
|
||||
Description: user.Description,
|
||||
AllowAPIKeyAuth: user.Filters.AllowAPIKeyAuth,
|
||||
},
|
||||
PublicKeys: user.PublicKeys,
|
||||
}
|
||||
render.JSON(w, r, resp)
|
||||
}
|
||||
|
||||
func changeUserAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) {
|
||||
func updateUserProfile(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
claims, err := getTokenClaims(r)
|
||||
if err != nil || claims.Username == "" {
|
||||
sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var req apiKeyAuth
|
||||
var req userProfile
|
||||
err = render.DecodeJSON(r.Body, &req)
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
|
||||
|
@ -385,12 +390,25 @@ func changeUserAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
user.Filters.AllowAPIKeyAuth = req.AllowAPIKeyAuth
|
||||
if !user.CanManagePublicKeys() && !user.CanChangeAPIKeyAuth() && !user.CanChangeInfo() {
|
||||
sendAPIResponse(w, r, nil, "You are not allowed to change anything", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if user.CanManagePublicKeys() {
|
||||
user.PublicKeys = req.PublicKeys
|
||||
}
|
||||
if user.CanChangeAPIKeyAuth() {
|
||||
user.Filters.AllowAPIKeyAuth = req.AllowAPIKeyAuth
|
||||
}
|
||||
if user.CanChangeInfo() {
|
||||
user.Email = req.Email
|
||||
user.Description = req.Description
|
||||
}
|
||||
if err := dataprovider.UpdateUser(&user); err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
sendAPIResponse(w, r, err, "API key authentication status updated", http.StatusOK)
|
||||
sendAPIResponse(w, r, err, "Profile updated", http.StatusOK)
|
||||
}
|
||||
|
||||
func changeUserPassword(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -28,8 +28,19 @@ type pwdChange struct {
|
|||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
||||
type apiKeyAuth struct {
|
||||
AllowAPIKeyAuth bool `json:"allow_api_key_auth"`
|
||||
type baseProfile struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
AllowAPIKeyAuth bool `json:"allow_api_key_auth"`
|
||||
}
|
||||
|
||||
type adminProfile struct {
|
||||
baseProfile
|
||||
}
|
||||
|
||||
type userProfile struct {
|
||||
baseProfile
|
||||
PublicKeys []string `json:"public_keys,omitempty"`
|
||||
}
|
||||
|
||||
func sendAPIResponse(w http.ResponseWriter, r *http.Request, err error, message string, code int) {
|
||||
|
|
311
httpd/httpd.go
311
httpd/httpd.go
|
@ -31,104 +31,101 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
logSender = "httpd"
|
||||
tokenPath = "/api/v2/token"
|
||||
logoutPath = "/api/v2/logout"
|
||||
userTokenPath = "/api/v2/user/token"
|
||||
userLogoutPath = "/api/v2/user/logout"
|
||||
activeConnectionsPath = "/api/v2/connections"
|
||||
quotasBasePath = "/api/v2/quotas"
|
||||
quotaScanPath = "/api/v2/quota-scans"
|
||||
quotaScanVFolderPath = "/api/v2/folder-quota-scans"
|
||||
userPath = "/api/v2/users"
|
||||
versionPath = "/api/v2/version"
|
||||
folderPath = "/api/v2/folders"
|
||||
serverStatusPath = "/api/v2/status"
|
||||
dumpDataPath = "/api/v2/dumpdata"
|
||||
loadDataPath = "/api/v2/loaddata"
|
||||
updateUsedQuotaPath = "/api/v2/quota-update"
|
||||
updateFolderUsedQuotaPath = "/api/v2/folder-quota-update"
|
||||
defenderHosts = "/api/v2/defender/hosts"
|
||||
defenderBanTime = "/api/v2/defender/bantime"
|
||||
defenderUnban = "/api/v2/defender/unban"
|
||||
defenderScore = "/api/v2/defender/score"
|
||||
adminPath = "/api/v2/admins"
|
||||
adminPwdPath = "/api/v2/admin/changepwd"
|
||||
adminPwdCompatPath = "/api/v2/changepwd/admin"
|
||||
adminManageAPIKeyPath = "/api/v2/admin/apikeyauth"
|
||||
userPwdPath = "/api/v2/user/changepwd"
|
||||
userPublicKeysPath = "/api/v2/user/publickeys"
|
||||
userFolderPath = "/api/v2/user/folder"
|
||||
userDirsPath = "/api/v2/user/dirs"
|
||||
userFilePath = "/api/v2/user/file"
|
||||
userFilesPath = "/api/v2/user/files"
|
||||
userStreamZipPath = "/api/v2/user/streamzip"
|
||||
apiKeysPath = "/api/v2/apikeys"
|
||||
adminTOTPConfigsPath = "/api/v2/admin/totp/configs"
|
||||
adminTOTPGeneratePath = "/api/v2/admin/totp/generate"
|
||||
adminTOTPValidatePath = "/api/v2/admin/totp/validate"
|
||||
adminTOTPSavePath = "/api/v2/admin/totp/save"
|
||||
admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes"
|
||||
userTOTPConfigsPath = "/api/v2/user/totp/configs"
|
||||
userTOTPGeneratePath = "/api/v2/user/totp/generate"
|
||||
userTOTPValidatePath = "/api/v2/user/totp/validate"
|
||||
userTOTPSavePath = "/api/v2/user/totp/save"
|
||||
user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes"
|
||||
userManageAPIKeyPath = "/api/v2/user/apikeyauth"
|
||||
retentionBasePath = "/api/v2/retention/users"
|
||||
retentionChecksPath = "/api/v2/retention/users/checks"
|
||||
healthzPath = "/healthz"
|
||||
webRootPathDefault = "/"
|
||||
webBasePathDefault = "/web"
|
||||
webBasePathAdminDefault = "/web/admin"
|
||||
webBasePathClientDefault = "/web/client"
|
||||
webAdminSetupPathDefault = "/web/admin/setup"
|
||||
webLoginPathDefault = "/web/admin/login"
|
||||
webAdminTwoFactorPathDefault = "/web/admin/twofactor"
|
||||
webAdminTwoFactorRecoveryPathDefault = "/web/admin/twofactor-recovery"
|
||||
webLogoutPathDefault = "/web/admin/logout"
|
||||
webUsersPathDefault = "/web/admin/users"
|
||||
webUserPathDefault = "/web/admin/user"
|
||||
webConnectionsPathDefault = "/web/admin/connections"
|
||||
webFoldersPathDefault = "/web/admin/folders"
|
||||
webFolderPathDefault = "/web/admin/folder"
|
||||
webStatusPathDefault = "/web/admin/status"
|
||||
webAdminsPathDefault = "/web/admin/managers"
|
||||
webAdminPathDefault = "/web/admin/manager"
|
||||
webMaintenancePathDefault = "/web/admin/maintenance"
|
||||
webBackupPathDefault = "/web/admin/backup"
|
||||
webRestorePathDefault = "/web/admin/restore"
|
||||
webScanVFolderPathDefault = "/web/admin/quotas/scanfolder"
|
||||
webQuotaScanPathDefault = "/web/admin/quotas/scanuser"
|
||||
webChangeAdminPwdPathDefault = "/web/admin/changepwd"
|
||||
webAdminCredentialsPathDefault = "/web/admin/credentials"
|
||||
webAdminMFAPathDefault = "/web/admin/mfa"
|
||||
webAdminTOTPGeneratePathDefault = "/web/admin/totp/generate"
|
||||
webAdminTOTPValidatePathDefault = "/web/admin/totp/validate"
|
||||
webAdminTOTPSavePathDefault = "/web/admin/totp/save"
|
||||
webAdminRecoveryCodesPathDefault = "/web/admin/recoverycodes"
|
||||
webChangeAdminAPIKeyAccessPathDefault = "/web/admin/apikeyaccess"
|
||||
webTemplateUserDefault = "/web/admin/template/user"
|
||||
webTemplateFolderDefault = "/web/admin/template/folder"
|
||||
webDefenderPathDefault = "/web/admin/defender"
|
||||
webDefenderHostsPathDefault = "/web/admin/defender/hosts"
|
||||
webClientLoginPathDefault = "/web/client/login"
|
||||
webClientTwoFactorPathDefault = "/web/client/twofactor"
|
||||
webClientTwoFactorRecoveryPathDefault = "/web/client/twofactor-recovery"
|
||||
webClientFilesPathDefault = "/web/client/files"
|
||||
webClientDirsPathDefault = "/web/client/dirs"
|
||||
webClientDownloadZipPathDefault = "/web/client/downloadzip"
|
||||
webClientCredentialsPathDefault = "/web/client/credentials"
|
||||
webClientMFAPathDefault = "/web/client/mfa"
|
||||
webClientTOTPGeneratePathDefault = "/web/client/totp/generate"
|
||||
webClientTOTPValidatePathDefault = "/web/client/totp/validate"
|
||||
webClientTOTPSavePathDefault = "/web/client/totp/save"
|
||||
webClientRecoveryCodesPathDefault = "/web/client/recoverycodes"
|
||||
webChangeClientPwdPathDefault = "/web/client/changepwd"
|
||||
webChangeClientKeysPathDefault = "/web/client/managekeys"
|
||||
webChangeClientAPIKeyAccessPathDefault = "/web/client/apikeyaccess"
|
||||
webClientLogoutPathDefault = "/web/client/logout"
|
||||
webStaticFilesPathDefault = "/static"
|
||||
logSender = "httpd"
|
||||
tokenPath = "/api/v2/token"
|
||||
logoutPath = "/api/v2/logout"
|
||||
userTokenPath = "/api/v2/user/token"
|
||||
userLogoutPath = "/api/v2/user/logout"
|
||||
activeConnectionsPath = "/api/v2/connections"
|
||||
quotasBasePath = "/api/v2/quotas"
|
||||
quotaScanPath = "/api/v2/quota-scans"
|
||||
quotaScanVFolderPath = "/api/v2/folder-quota-scans"
|
||||
userPath = "/api/v2/users"
|
||||
versionPath = "/api/v2/version"
|
||||
folderPath = "/api/v2/folders"
|
||||
serverStatusPath = "/api/v2/status"
|
||||
dumpDataPath = "/api/v2/dumpdata"
|
||||
loadDataPath = "/api/v2/loaddata"
|
||||
updateUsedQuotaPath = "/api/v2/quota-update"
|
||||
updateFolderUsedQuotaPath = "/api/v2/folder-quota-update"
|
||||
defenderHosts = "/api/v2/defender/hosts"
|
||||
defenderBanTime = "/api/v2/defender/bantime"
|
||||
defenderUnban = "/api/v2/defender/unban"
|
||||
defenderScore = "/api/v2/defender/score"
|
||||
adminPath = "/api/v2/admins"
|
||||
adminPwdPath = "/api/v2/admin/changepwd"
|
||||
adminPwdCompatPath = "/api/v2/changepwd/admin"
|
||||
adminProfilePath = "/api/v2/admin/profile"
|
||||
userPwdPath = "/api/v2/user/changepwd"
|
||||
userPublicKeysPath = "/api/v2/user/publickeys"
|
||||
userFolderPath = "/api/v2/user/folder"
|
||||
userDirsPath = "/api/v2/user/dirs"
|
||||
userFilePath = "/api/v2/user/file"
|
||||
userFilesPath = "/api/v2/user/files"
|
||||
userStreamZipPath = "/api/v2/user/streamzip"
|
||||
apiKeysPath = "/api/v2/apikeys"
|
||||
adminTOTPConfigsPath = "/api/v2/admin/totp/configs"
|
||||
adminTOTPGeneratePath = "/api/v2/admin/totp/generate"
|
||||
adminTOTPValidatePath = "/api/v2/admin/totp/validate"
|
||||
adminTOTPSavePath = "/api/v2/admin/totp/save"
|
||||
admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes"
|
||||
userTOTPConfigsPath = "/api/v2/user/totp/configs"
|
||||
userTOTPGeneratePath = "/api/v2/user/totp/generate"
|
||||
userTOTPValidatePath = "/api/v2/user/totp/validate"
|
||||
userTOTPSavePath = "/api/v2/user/totp/save"
|
||||
user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes"
|
||||
userProfilePath = "/api/v2/user/profile"
|
||||
retentionBasePath = "/api/v2/retention/users"
|
||||
retentionChecksPath = "/api/v2/retention/users/checks"
|
||||
healthzPath = "/healthz"
|
||||
webRootPathDefault = "/"
|
||||
webBasePathDefault = "/web"
|
||||
webBasePathAdminDefault = "/web/admin"
|
||||
webBasePathClientDefault = "/web/client"
|
||||
webAdminSetupPathDefault = "/web/admin/setup"
|
||||
webLoginPathDefault = "/web/admin/login"
|
||||
webAdminTwoFactorPathDefault = "/web/admin/twofactor"
|
||||
webAdminTwoFactorRecoveryPathDefault = "/web/admin/twofactor-recovery"
|
||||
webLogoutPathDefault = "/web/admin/logout"
|
||||
webUsersPathDefault = "/web/admin/users"
|
||||
webUserPathDefault = "/web/admin/user"
|
||||
webConnectionsPathDefault = "/web/admin/connections"
|
||||
webFoldersPathDefault = "/web/admin/folders"
|
||||
webFolderPathDefault = "/web/admin/folder"
|
||||
webStatusPathDefault = "/web/admin/status"
|
||||
webAdminsPathDefault = "/web/admin/managers"
|
||||
webAdminPathDefault = "/web/admin/manager"
|
||||
webMaintenancePathDefault = "/web/admin/maintenance"
|
||||
webBackupPathDefault = "/web/admin/backup"
|
||||
webRestorePathDefault = "/web/admin/restore"
|
||||
webScanVFolderPathDefault = "/web/admin/quotas/scanfolder"
|
||||
webQuotaScanPathDefault = "/web/admin/quotas/scanuser"
|
||||
webChangeAdminPwdPathDefault = "/web/admin/changepwd"
|
||||
webAdminProfilePathDefault = "/web/admin/profile"
|
||||
webAdminMFAPathDefault = "/web/admin/mfa"
|
||||
webAdminTOTPGeneratePathDefault = "/web/admin/totp/generate"
|
||||
webAdminTOTPValidatePathDefault = "/web/admin/totp/validate"
|
||||
webAdminTOTPSavePathDefault = "/web/admin/totp/save"
|
||||
webAdminRecoveryCodesPathDefault = "/web/admin/recoverycodes"
|
||||
webTemplateUserDefault = "/web/admin/template/user"
|
||||
webTemplateFolderDefault = "/web/admin/template/folder"
|
||||
webDefenderPathDefault = "/web/admin/defender"
|
||||
webDefenderHostsPathDefault = "/web/admin/defender/hosts"
|
||||
webClientLoginPathDefault = "/web/client/login"
|
||||
webClientTwoFactorPathDefault = "/web/client/twofactor"
|
||||
webClientTwoFactorRecoveryPathDefault = "/web/client/twofactor-recovery"
|
||||
webClientFilesPathDefault = "/web/client/files"
|
||||
webClientDirsPathDefault = "/web/client/dirs"
|
||||
webClientDownloadZipPathDefault = "/web/client/downloadzip"
|
||||
webClientProfilePathDefault = "/web/client/profile"
|
||||
webClientMFAPathDefault = "/web/client/mfa"
|
||||
webClientTOTPGeneratePathDefault = "/web/client/totp/generate"
|
||||
webClientTOTPValidatePathDefault = "/web/client/totp/validate"
|
||||
webClientTOTPSavePathDefault = "/web/client/totp/save"
|
||||
webClientRecoveryCodesPathDefault = "/web/client/recoverycodes"
|
||||
webChangeClientPwdPathDefault = "/web/client/changepwd"
|
||||
webClientLogoutPathDefault = "/web/client/logout"
|
||||
webStaticFilesPathDefault = "/static"
|
||||
// MaxRestoreSize defines the max size for the loaddata input file
|
||||
MaxRestoreSize = 10485760 // 10 MB
|
||||
maxRequestSize = 1048576 // 1MB
|
||||
|
@ -139,63 +136,60 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
backupsPath string
|
||||
certMgr *common.CertManager
|
||||
cleanupTicker *time.Ticker
|
||||
cleanupDone chan bool
|
||||
invalidatedJWTTokens sync.Map
|
||||
csrfTokenAuth *jwtauth.JWTAuth
|
||||
webRootPath string
|
||||
webBasePath string
|
||||
webBaseAdminPath string
|
||||
webBaseClientPath string
|
||||
webAdminSetupPath string
|
||||
webLoginPath string
|
||||
webAdminTwoFactorPath string
|
||||
webAdminTwoFactorRecoveryPath string
|
||||
webLogoutPath string
|
||||
webUsersPath string
|
||||
webUserPath string
|
||||
webConnectionsPath string
|
||||
webFoldersPath string
|
||||
webFolderPath string
|
||||
webStatusPath string
|
||||
webAdminsPath string
|
||||
webAdminPath string
|
||||
webMaintenancePath string
|
||||
webBackupPath string
|
||||
webRestorePath string
|
||||
webScanVFolderPath string
|
||||
webQuotaScanPath string
|
||||
webAdminCredentialsPath string
|
||||
webAdminMFAPath string
|
||||
webAdminTOTPGeneratePath string
|
||||
webAdminTOTPValidatePath string
|
||||
webAdminTOTPSavePath string
|
||||
webAdminRecoveryCodesPath string
|
||||
webChangeAdminAPIKeyAccessPath string
|
||||
webChangeAdminPwdPath string
|
||||
webTemplateUser string
|
||||
webTemplateFolder string
|
||||
webDefenderPath string
|
||||
webDefenderHostsPath string
|
||||
webClientLoginPath string
|
||||
webClientTwoFactorPath string
|
||||
webClientTwoFactorRecoveryPath string
|
||||
webClientFilesPath string
|
||||
webClientDirsPath string
|
||||
webClientDownloadZipPath string
|
||||
webClientCredentialsPath string
|
||||
webChangeClientPwdPath string
|
||||
webChangeClientKeysPath string
|
||||
webClientMFAPath string
|
||||
webClientTOTPGeneratePath string
|
||||
webClientTOTPValidatePath string
|
||||
webClientTOTPSavePath string
|
||||
webClientRecoveryCodesPath string
|
||||
webChangeClientAPIKeyAccessPath string
|
||||
webClientLogoutPath string
|
||||
webStaticFilesPath string
|
||||
backupsPath string
|
||||
certMgr *common.CertManager
|
||||
cleanupTicker *time.Ticker
|
||||
cleanupDone chan bool
|
||||
invalidatedJWTTokens sync.Map
|
||||
csrfTokenAuth *jwtauth.JWTAuth
|
||||
webRootPath string
|
||||
webBasePath string
|
||||
webBaseAdminPath string
|
||||
webBaseClientPath string
|
||||
webAdminSetupPath string
|
||||
webLoginPath string
|
||||
webAdminTwoFactorPath string
|
||||
webAdminTwoFactorRecoveryPath string
|
||||
webLogoutPath string
|
||||
webUsersPath string
|
||||
webUserPath string
|
||||
webConnectionsPath string
|
||||
webFoldersPath string
|
||||
webFolderPath string
|
||||
webStatusPath string
|
||||
webAdminsPath string
|
||||
webAdminPath string
|
||||
webMaintenancePath string
|
||||
webBackupPath string
|
||||
webRestorePath string
|
||||
webScanVFolderPath string
|
||||
webQuotaScanPath string
|
||||
webAdminProfilePath string
|
||||
webAdminMFAPath string
|
||||
webAdminTOTPGeneratePath string
|
||||
webAdminTOTPValidatePath string
|
||||
webAdminTOTPSavePath string
|
||||
webAdminRecoveryCodesPath string
|
||||
webChangeAdminPwdPath string
|
||||
webTemplateUser string
|
||||
webTemplateFolder string
|
||||
webDefenderPath string
|
||||
webDefenderHostsPath string
|
||||
webClientLoginPath string
|
||||
webClientTwoFactorPath string
|
||||
webClientTwoFactorRecoveryPath string
|
||||
webClientFilesPath string
|
||||
webClientDirsPath string
|
||||
webClientDownloadZipPath string
|
||||
webClientProfilePath string
|
||||
webChangeClientPwdPath string
|
||||
webClientMFAPath string
|
||||
webClientTOTPGeneratePath string
|
||||
webClientTOTPValidatePath string
|
||||
webClientTOTPSavePath string
|
||||
webClientRecoveryCodesPath string
|
||||
webClientLogoutPath string
|
||||
webStaticFilesPath string
|
||||
// max upload size for http clients, 1GB by default
|
||||
maxUploadFileSize = int64(1048576000)
|
||||
)
|
||||
|
@ -530,10 +524,8 @@ func updateWebClientURLs(baseURL string) {
|
|||
webClientFilesPath = path.Join(baseURL, webClientFilesPathDefault)
|
||||
webClientDirsPath = path.Join(baseURL, webClientDirsPathDefault)
|
||||
webClientDownloadZipPath = path.Join(baseURL, webClientDownloadZipPathDefault)
|
||||
webClientCredentialsPath = path.Join(baseURL, webClientCredentialsPathDefault)
|
||||
webClientProfilePath = path.Join(baseURL, webClientProfilePathDefault)
|
||||
webChangeClientPwdPath = path.Join(baseURL, webChangeClientPwdPathDefault)
|
||||
webChangeClientKeysPath = path.Join(baseURL, webChangeClientKeysPathDefault)
|
||||
webChangeClientAPIKeyAccessPath = path.Join(baseURL, webChangeClientAPIKeyAccessPathDefault)
|
||||
webClientLogoutPath = path.Join(baseURL, webClientLogoutPathDefault)
|
||||
webClientMFAPath = path.Join(baseURL, webClientMFAPathDefault)
|
||||
webClientTOTPGeneratePath = path.Join(baseURL, webClientTOTPGeneratePathDefault)
|
||||
|
@ -568,13 +560,12 @@ func updateWebAdminURLs(baseURL string) {
|
|||
webScanVFolderPath = path.Join(baseURL, webScanVFolderPathDefault)
|
||||
webQuotaScanPath = path.Join(baseURL, webQuotaScanPathDefault)
|
||||
webChangeAdminPwdPath = path.Join(baseURL, webChangeAdminPwdPathDefault)
|
||||
webAdminCredentialsPath = path.Join(baseURL, webAdminCredentialsPathDefault)
|
||||
webAdminProfilePath = path.Join(baseURL, webAdminProfilePathDefault)
|
||||
webAdminMFAPath = path.Join(baseURL, webAdminMFAPathDefault)
|
||||
webAdminTOTPGeneratePath = path.Join(baseURL, webAdminTOTPGeneratePathDefault)
|
||||
webAdminTOTPValidatePath = path.Join(baseURL, webAdminTOTPValidatePathDefault)
|
||||
webAdminTOTPSavePath = path.Join(baseURL, webAdminTOTPSavePathDefault)
|
||||
webAdminRecoveryCodesPath = path.Join(baseURL, webAdminRecoveryCodesPathDefault)
|
||||
webChangeAdminAPIKeyAccessPath = path.Join(baseURL, webChangeAdminAPIKeyAccessPathDefault)
|
||||
webTemplateUser = path.Join(baseURL, webTemplateUserDefault)
|
||||
webTemplateFolder = path.Join(baseURL, webTemplateFolderDefault)
|
||||
webDefenderHostsPath = path.Join(baseURL, webDefenderHostsPathDefault)
|
||||
|
|
|
@ -92,13 +92,13 @@ const (
|
|||
adminTOTPValidatePath = "/api/v2/admin/totp/validate"
|
||||
adminTOTPSavePath = "/api/v2/admin/totp/save"
|
||||
admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes"
|
||||
adminManageAPIKeyPath = "/api/v2/admin/apikeyauth"
|
||||
adminProfilePath = "/api/v2/admin/profile"
|
||||
userTOTPConfigsPath = "/api/v2/user/totp/configs"
|
||||
userTOTPGeneratePath = "/api/v2/user/totp/generate"
|
||||
userTOTPValidatePath = "/api/v2/user/totp/validate"
|
||||
userTOTPSavePath = "/api/v2/user/totp/save"
|
||||
user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes"
|
||||
userManageAPIKeyPath = "/api/v2/user/apikeyauth"
|
||||
userProfilePath = "/api/v2/user/profile"
|
||||
retentionBasePath = "/api/v2/retention/users"
|
||||
healthzPath = "/healthz"
|
||||
webBasePath = "/web"
|
||||
|
@ -117,11 +117,10 @@ const (
|
|||
webMaintenancePath = "/web/admin/maintenance"
|
||||
webRestorePath = "/web/admin/restore"
|
||||
webChangeAdminPwdPath = "/web/admin/changepwd"
|
||||
webAdminCredentialsPath = "/web/admin/credentials"
|
||||
webAdminProfilePath = "/web/admin/profile"
|
||||
webTemplateUser = "/web/admin/template/user"
|
||||
webTemplateFolder = "/web/admin/template/folder"
|
||||
webDefenderPath = "/web/admin/defender"
|
||||
webChangeAdminAPIKeyAccessPath = "/web/admin/apikeyaccess"
|
||||
webAdminTwoFactorPath = "/web/admin/twofactor"
|
||||
webAdminTwoFactorRecoveryPath = "/web/admin/twofactor-recovery"
|
||||
webAdminMFAPath = "/web/admin/mfa"
|
||||
|
@ -131,10 +130,8 @@ const (
|
|||
webClientFilesPath = "/web/client/files"
|
||||
webClientDirsPath = "/web/client/dirs"
|
||||
webClientDownloadZipPath = "/web/client/downloadzip"
|
||||
webClientCredentialsPath = "/web/client/credentials"
|
||||
webChangeClientPwdPath = "/web/client/changepwd"
|
||||
webChangeClientKeysPath = "/web/client/managekeys"
|
||||
webChangeClientAPIKeyAccessPath = "/web/client/apikeyaccess"
|
||||
webClientProfilePath = "/web/client/profile"
|
||||
webClientTwoFactorPath = "/web/client/twofactor"
|
||||
webClientTwoFactorRecoveryPath = "/web/client/twofactor-recovery"
|
||||
webClientLogoutPath = "/web/client/logout"
|
||||
|
@ -3408,7 +3405,7 @@ func TestSkipNaturalKeysValidation(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
form := make(url.Values)
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
req, err := http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, err := http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
|
@ -3438,7 +3435,7 @@ func TestSkipNaturalKeysValidation(t *testing.T) {
|
|||
apiKeyAuthReq["allow_api_key_auth"] = true
|
||||
asJSON, err := json.Marshal(apiKeyAuthReq)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPut, userManageAPIKeyPath, bytes.NewBuffer(asJSON))
|
||||
req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, userAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -3456,7 +3453,7 @@ func TestSkipNaturalKeysValidation(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
form = make(url.Values)
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeAdminAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
|
@ -3467,7 +3464,7 @@ func TestSkipNaturalKeysValidation(t *testing.T) {
|
|||
apiKeyAuthReq["allow_api_key_auth"] = true
|
||||
asJSON, err = json.Marshal(apiKeyAuthReq)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPut, adminManageAPIKeyPath, bytes.NewBuffer(asJSON))
|
||||
req, err = http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer(asJSON))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, adminAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -5128,6 +5125,40 @@ func TestChangeAdminPwdInvalidJsonMock(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
}
|
||||
|
||||
func TestMFAPermission(t *testing.T) {
|
||||
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, webClientMFAPath, nil)
|
||||
assert.NoError(t, err)
|
||||
req.RequestURI = webClientMFAPath
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
user.Filters.WebClient = []string{sdk.WebClientMFADisabled}
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
webToken, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, webClientMFAPath, nil)
|
||||
assert.NoError(t, err)
|
||||
req.RequestURI = webClientMFAPath
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestWebUserTwoFactorLogin(t *testing.T) {
|
||||
u := getTestUser()
|
||||
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
||||
|
@ -5958,104 +5989,160 @@ func TestWebUserTOTP(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
}
|
||||
|
||||
func TestWebAPIChangeUserAPIKeyAuth(t *testing.T) {
|
||||
func TestWebAPIChangeUserProfileMock(t *testing.T) {
|
||||
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, user.Filters.AllowAPIKeyAuth)
|
||||
token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
// invalid json
|
||||
req, err := http.NewRequest(http.MethodPut, userManageAPIKeyPath, bytes.NewBuffer([]byte("{")))
|
||||
req, err := http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer([]byte("{")))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
|
||||
apiKeyAuthReq := make(map[string]bool)
|
||||
apiKeyAuthReq["allow_api_key_auth"] = true
|
||||
asJSON, err := json.Marshal(apiKeyAuthReq)
|
||||
email := "userapi@example.com"
|
||||
description := "user API description"
|
||||
profileReq := make(map[string]interface{})
|
||||
profileReq["allow_api_key_auth"] = true
|
||||
profileReq["email"] = email
|
||||
profileReq["description"] = description
|
||||
profileReq["public_keys"] = []string{testPubKey, testPubKey1}
|
||||
asJSON, err := json.Marshal(profileReq)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPut, userManageAPIKeyPath, bytes.NewBuffer(asJSON))
|
||||
req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, user.Filters.AllowAPIKeyAuth)
|
||||
|
||||
apiKeyAuthReq = make(map[string]bool)
|
||||
req, err = http.NewRequest(http.MethodGet, userManageAPIKeyPath, nil)
|
||||
profileReq = make(map[string]interface{})
|
||||
req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &apiKeyAuthReq)
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &profileReq)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, apiKeyAuthReq["allow_api_key_auth"])
|
||||
assert.Equal(t, email, profileReq["email"].(string))
|
||||
assert.Equal(t, description, profileReq["description"].(string))
|
||||
assert.True(t, profileReq["allow_api_key_auth"].(bool))
|
||||
assert.Len(t, profileReq["public_keys"].([]interface{}), 2)
|
||||
// set an invalid email
|
||||
profileReq = make(map[string]interface{})
|
||||
profileReq["email"] = "notavalidemail"
|
||||
asJSON, err = json.Marshal(profileReq)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Validation error: email")
|
||||
// set an invalid public key
|
||||
profileReq = make(map[string]interface{})
|
||||
profileReq["public_keys"] = []string{"not a public key"}
|
||||
asJSON, err = json.Marshal(profileReq)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Validation error: could not parse key")
|
||||
|
||||
apiKeyAuthReq["allow_api_key_auth"] = false
|
||||
asJSON, err = json.Marshal(apiKeyAuthReq)
|
||||
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientPubKeyChangeDisabled}
|
||||
user.Email = email
|
||||
user.Description = description
|
||||
user.Filters.AllowAPIKeyAuth = true
|
||||
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPut, userManageAPIKeyPath, bytes.NewBuffer(asJSON))
|
||||
token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
|
||||
profileReq = make(map[string]interface{})
|
||||
profileReq["allow_api_key_auth"] = false
|
||||
profileReq["email"] = email
|
||||
profileReq["description"] = description + "_mod"
|
||||
profileReq["public_keys"] = []string{testPubKey}
|
||||
asJSON, err = json.Marshal(profileReq)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, user.Filters.AllowAPIKeyAuth)
|
||||
|
||||
apiKeyAuthReq = make(map[string]bool)
|
||||
req, err = http.NewRequest(http.MethodGet, userManageAPIKeyPath, nil)
|
||||
assert.Contains(t, rr.Body.String(), "Profile updated")
|
||||
// check that api key auth and public keys were not changed
|
||||
profileReq = make(map[string]interface{})
|
||||
req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &apiKeyAuthReq)
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &profileReq)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, apiKeyAuthReq["allow_api_key_auth"])
|
||||
assert.Equal(t, email, profileReq["email"].(string))
|
||||
assert.Equal(t, description+"_mod", profileReq["description"].(string))
|
||||
assert.True(t, profileReq["allow_api_key_auth"].(bool))
|
||||
assert.Len(t, profileReq["public_keys"].([]interface{}), 2)
|
||||
|
||||
// remove the permission
|
||||
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled}
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled}
|
||||
user.Description = description + "_mod"
|
||||
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, user.Filters.WebClient, 1)
|
||||
assert.Contains(t, user.Filters.WebClient, sdk.WebClientAPIKeyAuthChangeDisabled)
|
||||
|
||||
newToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
|
||||
apiKeyAuthReq["allow_api_key_auth"] = true
|
||||
asJSON, err = json.Marshal(apiKeyAuthReq)
|
||||
profileReq = make(map[string]interface{})
|
||||
profileReq["allow_api_key_auth"] = false
|
||||
profileReq["email"] = "newemail@apiuser.com"
|
||||
profileReq["description"] = description
|
||||
profileReq["public_keys"] = []string{testPubKey}
|
||||
asJSON, err = json.Marshal(profileReq)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPut, userManageAPIKeyPath, bytes.NewBuffer(asJSON))
|
||||
req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, newToken)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
profileReq = make(map[string]interface{})
|
||||
req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &profileReq)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, email, profileReq["email"].(string))
|
||||
assert.Equal(t, description+"_mod", profileReq["description"].(string))
|
||||
assert.True(t, profileReq["allow_api_key_auth"].(bool))
|
||||
assert.Len(t, profileReq["public_keys"].([]interface{}), 1)
|
||||
// finally disable all profile permissions
|
||||
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled,
|
||||
sdk.WebClientPubKeyChangeDisabled}
|
||||
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
// get will still work
|
||||
req, err = http.NewRequest(http.MethodGet, userManageAPIKeyPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, newToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "You are not allowed to change anything")
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
|
||||
apiKeyAuthReq = make(map[string]bool)
|
||||
req, err = http.NewRequest(http.MethodGet, userManageAPIKeyPath, nil)
|
||||
req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodPut, userManageAPIKeyPath, bytes.NewBuffer(asJSON))
|
||||
req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
|
@ -6136,7 +6223,7 @@ func TestLoginInvalidPasswordMock(t *testing.T) {
|
|||
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
}
|
||||
|
||||
func TestChangeAdminAPIKeyAuth(t *testing.T) {
|
||||
func TestWebAPIChangeAdminProfileMock(t *testing.T) {
|
||||
admin := getTestAdmin()
|
||||
admin.Username = altAdminUsername
|
||||
admin.Password = altAdminPassword
|
||||
|
@ -6147,65 +6234,59 @@ func TestChangeAdminAPIKeyAuth(t *testing.T) {
|
|||
token, err := getJWTAPITokenFromTestServer(altAdminUsername, altAdminPassword)
|
||||
assert.NoError(t, err)
|
||||
// invalid json
|
||||
req, err := http.NewRequest(http.MethodPut, adminManageAPIKeyPath, bytes.NewBuffer([]byte("{")))
|
||||
req, err := http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer([]byte("{")))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
|
||||
apiKeyAuthReq := make(map[string]bool)
|
||||
apiKeyAuthReq["allow_api_key_auth"] = true
|
||||
asJSON, err := json.Marshal(apiKeyAuthReq)
|
||||
email := "adminapi@example.com"
|
||||
description := "admin API description"
|
||||
profileReq := make(map[string]interface{})
|
||||
profileReq["allow_api_key_auth"] = true
|
||||
profileReq["email"] = email
|
||||
profileReq["description"] = description
|
||||
asJSON, err := json.Marshal(profileReq)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPut, adminManageAPIKeyPath, bytes.NewBuffer(asJSON))
|
||||
req, err = http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer(asJSON))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Profile updated")
|
||||
|
||||
admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, admin.Filters.AllowAPIKeyAuth)
|
||||
|
||||
apiKeyAuthReq = make(map[string]bool)
|
||||
req, err = http.NewRequest(http.MethodGet, adminManageAPIKeyPath, nil)
|
||||
profileReq = make(map[string]interface{})
|
||||
req, err = http.NewRequest(http.MethodGet, adminProfilePath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &apiKeyAuthReq)
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &profileReq)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, apiKeyAuthReq["allow_api_key_auth"])
|
||||
|
||||
apiKeyAuthReq["allow_api_key_auth"] = false
|
||||
asJSON, err = json.Marshal(apiKeyAuthReq)
|
||||
assert.Equal(t, email, profileReq["email"].(string))
|
||||
assert.Equal(t, description, profileReq["description"].(string))
|
||||
assert.True(t, profileReq["allow_api_key_auth"].(bool))
|
||||
// set an invalid email
|
||||
profileReq["email"] = "admin_invalid_email"
|
||||
asJSON, err = json.Marshal(profileReq)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPut, adminManageAPIKeyPath, bytes.NewBuffer(asJSON))
|
||||
req, err = http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer(asJSON))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
apiKeyAuthReq = make(map[string]bool)
|
||||
req, err = http.NewRequest(http.MethodGet, adminManageAPIKeyPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &apiKeyAuthReq)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, apiKeyAuthReq["allow_api_key_auth"])
|
||||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Validation error: email")
|
||||
|
||||
_, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, adminManageAPIKeyPath, nil)
|
||||
req, err = http.NewRequest(http.MethodGet, adminProfilePath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodPut, adminManageAPIKeyPath, bytes.NewBuffer(asJSON))
|
||||
req, err = http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer(asJSON))
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
|
@ -7473,13 +7554,13 @@ func TestWebAPILoginMock(t *testing.T) {
|
|||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
// API token is not valid for web usage
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientCredentialsPath, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
|
||||
setJWTCookieForReq(req, apiToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusFound, rr)
|
||||
assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientCredentialsPath, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
@ -7509,13 +7590,13 @@ func TestWebClientLoginMock(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusFound, rr)
|
||||
assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
|
||||
// bearer should not work
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientCredentialsPath, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
|
||||
setBearerForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusFound, rr)
|
||||
assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
|
||||
// now try to render client pages
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientCredentialsPath, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
@ -7529,7 +7610,7 @@ func TestWebClientLoginMock(t *testing.T) {
|
|||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusFound, rr)
|
||||
assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientCredentialsPath, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusFound, rr)
|
||||
|
@ -7545,7 +7626,7 @@ func TestWebClientLoginMock(t *testing.T) {
|
|||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientCredentialsPath, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusInternalServerError, rr)
|
||||
|
@ -7602,7 +7683,7 @@ func TestWebClientLoginMock(t *testing.T) {
|
|||
form := make(url.Values)
|
||||
form.Set("public_keys", testPubKey)
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -7864,15 +7945,22 @@ func TestWebClientChangePwd(t *testing.T) {
|
|||
csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, webChangeClientPwdPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
form := make(url.Values)
|
||||
form.Set("current_password", defaultPassword)
|
||||
form.Set("new_password1", defaultPassword)
|
||||
form.Set("new_password2", defaultPassword)
|
||||
// no csrf token
|
||||
req, _ := http.NewRequest(http.MethodPost, webChangeClientPwdPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, err = http.NewRequest(http.MethodPost, webChangeClientPwdPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr := executeRequest(req)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
assert.Contains(t, rr.Body.String(), "unable to verify form token")
|
||||
|
||||
|
@ -8004,64 +8092,6 @@ func TestWebAPIPublicKeys(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestWebClientChangePubKeys(t *testing.T) {
|
||||
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
|
||||
assert.NoError(t, err)
|
||||
form := make(url.Values)
|
||||
form.Set("public_keys", testPubKey)
|
||||
form.Add("public_keys", testPubKey1)
|
||||
// no csrf token
|
||||
req, _ := http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
assert.Contains(t, rr.Body.String(), "unable to verify form token")
|
||||
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Your public keys has been successfully updated")
|
||||
|
||||
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, user.PublicKeys, 2)
|
||||
|
||||
form.Set("public_keys", "invalid")
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Validation error: could not parse key")
|
||||
|
||||
user.Filters.WebClient = append(user.Filters.WebClient, sdk.WebClientPubKeyChangeDisabled)
|
||||
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
webToken, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
form.Set("public_keys", testPubKey)
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.RequestURI = webChangeClientKeysPath
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestPreDownloadHook(t *testing.T) {
|
||||
if runtime.GOOS == osWindows {
|
||||
t.Skip("this test is not available on Windows")
|
||||
|
@ -9743,7 +9773,7 @@ func TestWebAdminLoginMock(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAdminNoToken(t *testing.T) {
|
||||
req, _ := http.NewRequest(http.MethodGet, webAdminCredentialsPath, nil)
|
||||
req, _ := http.NewRequest(http.MethodGet, webAdminProfilePath, nil)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusFound, rr)
|
||||
assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
|
||||
|
@ -9762,10 +9792,8 @@ func TestAdminNoToken(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusUnauthorized, rr)
|
||||
}
|
||||
|
||||
func TestWebUserAllowAPIKeyAuth(t *testing.T) {
|
||||
u := getTestUser()
|
||||
u.Filters.AllowAPIKeyAuth = true
|
||||
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
||||
func TestWebUserProfile(t *testing.T) {
|
||||
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
|
||||
|
@ -9773,10 +9801,17 @@ func TestWebUserAllowAPIKeyAuth(t *testing.T) {
|
|||
token, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
|
||||
email := "user@user.com"
|
||||
description := "User"
|
||||
|
||||
form := make(url.Values)
|
||||
form.Set("allow_api_key_auth", "1")
|
||||
form.Set("email", email)
|
||||
form.Set("description", description)
|
||||
form.Set("public_keys", testPubKey)
|
||||
form.Add("public_keys", testPubKey1)
|
||||
// no csrf token
|
||||
req, err := http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, err := http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
|
@ -9785,43 +9820,109 @@ func TestWebUserAllowAPIKeyAuth(t *testing.T) {
|
|||
assert.Contains(t, rr.Body.String(), "unable to verify form token")
|
||||
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "API key authentication updated")
|
||||
assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
|
||||
|
||||
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, user.Filters.AllowAPIKeyAuth)
|
||||
assert.Len(t, user.PublicKeys, 2)
|
||||
assert.Equal(t, email, user.Email)
|
||||
assert.Equal(t, description, user.Description)
|
||||
|
||||
form = make(url.Values)
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
// set an invalid email
|
||||
form.Set("email", "not an email")
|
||||
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "API key authentication updated")
|
||||
assert.Contains(t, rr.Body.String(), "Validation error: email")
|
||||
// invalid public key
|
||||
form.Set("email", email)
|
||||
form.Set("public_keys", "invalid")
|
||||
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Validation error: could not parse key")
|
||||
// now remove permissions
|
||||
form.Set("public_keys", testPubKey)
|
||||
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled}
|
||||
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
token, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
|
||||
form.Set("allow_api_key_auth", "0")
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
|
||||
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, user.Filters.AllowAPIKeyAuth)
|
||||
assert.True(t, user.Filters.AllowAPIKeyAuth)
|
||||
assert.Len(t, user.PublicKeys, 1)
|
||||
assert.Equal(t, email, user.Email)
|
||||
assert.Equal(t, description, user.Description)
|
||||
|
||||
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled}
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientPubKeyChangeDisabled}
|
||||
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, user.CanChangeAPIKeyAuth())
|
||||
|
||||
newToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
token, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
form = make(url.Values)
|
||||
form.Set("allow_api_key_auth", "1")
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
form.Set("public_keys", testPubKey)
|
||||
form.Add("public_keys", testPubKey1)
|
||||
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, newToken)
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
|
||||
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, user.Filters.AllowAPIKeyAuth)
|
||||
assert.Len(t, user.PublicKeys, 1)
|
||||
assert.Equal(t, email, user.Email)
|
||||
assert.Equal(t, description, user.Description)
|
||||
|
||||
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled}
|
||||
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
token, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
form.Set("email", "newemail@user.com")
|
||||
form.Set("description", "new description")
|
||||
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
|
||||
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, user.Filters.AllowAPIKeyAuth)
|
||||
assert.Len(t, user.PublicKeys, 2)
|
||||
assert.Equal(t, email, user.Email)
|
||||
assert.Equal(t, description, user.Description)
|
||||
// finally disable all profile permissions
|
||||
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled,
|
||||
sdk.WebClientPubKeyChangeDisabled}
|
||||
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
token, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
|
@ -9832,14 +9933,14 @@ func TestWebUserAllowAPIKeyAuth(t *testing.T) {
|
|||
|
||||
form = make(url.Values)
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusInternalServerError, rr)
|
||||
}
|
||||
|
||||
func TestWebAdminAllowAPIKeyAuth(t *testing.T) {
|
||||
func TestWebAdminProfile(t *testing.T) {
|
||||
admin := getTestAdmin()
|
||||
admin.Username = altAdminUsername
|
||||
admin.Password = altAdminPassword
|
||||
|
@ -9849,48 +9950,60 @@ func TestWebAdminAllowAPIKeyAuth(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
|
||||
assert.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodGet, webAdminProfilePath, nil)
|
||||
assert.NoError(t, err)
|
||||
setJWTCookieForReq(req, token)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
form := make(url.Values)
|
||||
form.Set("allow_api_key_auth", "1")
|
||||
form.Set("email", "admin@example.com")
|
||||
form.Set("description", "admin desc")
|
||||
// no csrf token
|
||||
req, err := http.NewRequest(http.MethodPost, webChangeAdminAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, err = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
assert.NoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr := executeRequest(req)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
assert.Contains(t, rr.Body.String(), "unable to verify form token")
|
||||
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeAdminAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "API key authentication updated")
|
||||
assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
|
||||
|
||||
admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, admin.Filters.AllowAPIKeyAuth)
|
||||
assert.Equal(t, "admin@example.com", admin.Email)
|
||||
assert.Equal(t, "admin desc", admin.Description)
|
||||
|
||||
form = make(url.Values)
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeAdminAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "API key authentication updated")
|
||||
assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
|
||||
|
||||
admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, admin.Filters.AllowAPIKeyAuth)
|
||||
assert.Empty(t, admin.Email)
|
||||
assert.Empty(t, admin.Description)
|
||||
|
||||
_, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
form = make(url.Values)
|
||||
form.Set(csrfFormToken, csrfToken)
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeAdminAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
|
@ -9908,7 +10021,7 @@ func TestWebAdminPwdChange(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
|
||||
assert.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodGet, webAdminCredentialsPath, nil)
|
||||
req, err := http.NewRequest(http.MethodGet, webChangeAdminPwdPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setJWTCookieForReq(req, token)
|
||||
rr := executeRequest(req)
|
||||
|
|
|
@ -411,22 +411,22 @@ func TestInvalidToken(t *testing.T) {
|
|||
assert.Contains(t, rr.Body.String(), "Invalid token claims")
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
getUserAPIKeyAuthStatus(rr, req)
|
||||
getUserProfile(rr, req)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Invalid token claims")
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
changeUserAPIKeyAuthStatus(rr, req)
|
||||
updateUserProfile(rr, req)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Invalid token claims")
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
getAdminAPIKeyAuthStatus(rr, req)
|
||||
getAdminProfile(rr, req)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Invalid token claims")
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
changeAdminAPIKeyAuthStatus(rr, req)
|
||||
updateAdminProfile(rr, req)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Invalid token claims")
|
||||
|
||||
|
@ -577,9 +577,8 @@ func TestCreateTokenError(t *testing.T) {
|
|||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr = httptest.NewRecorder()
|
||||
handleWebAdminChangePwdPost(rr, req)
|
||||
// the claim is invalid so we fail to render the client page since
|
||||
// we have to load the logged admin
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String())
|
||||
assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
|
||||
assert.Contains(t, rr.Body.String(), "invalid URL escape")
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, webLoginPath+"?a=a%C3%A2%G3", nil)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
@ -596,24 +595,19 @@ func TestCreateTokenError(t *testing.T) {
|
|||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr = httptest.NewRecorder()
|
||||
handleWebClientChangePwdPost(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String())
|
||||
assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
|
||||
assert.Contains(t, rr.Body.String(), "invalid URL escape")
|
||||
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath+"?a=a%C3%AO%GB", bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath+"?a=a%C3%AO%GB", bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr = httptest.NewRecorder()
|
||||
handleWebClientManageKeysPost(rr, req)
|
||||
handleWebClientProfilePost(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String())
|
||||
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath+"?a=a%C3%AO%GA", bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath+"?a=a%C3%AO%GB", bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr = httptest.NewRecorder()
|
||||
handleWebClientManageAPIKeyPost(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String())
|
||||
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeAdminAPIKeyAccessPath+"?a=a%C3%AO%GB", bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr = httptest.NewRecorder()
|
||||
handleWebAdminManageAPIKeyPost(rr, req)
|
||||
handleWebAdminProfilePost(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String())
|
||||
|
||||
req, _ = http.NewRequest(http.MethodPost, webAdminTwoFactorPath+"?a=a%C3%AO%GC", bytes.NewBuffer([]byte(form.Encode())))
|
||||
|
@ -754,8 +748,8 @@ func TestJWTTokenValidation(t *testing.T) {
|
|||
permClientFn := checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)
|
||||
fn = permClientFn(r)
|
||||
rr = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, nil)
|
||||
req.RequestURI = webChangeClientKeysPath
|
||||
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, nil)
|
||||
req.RequestURI = webClientProfilePath
|
||||
ctx = jwtauth.NewContext(req.Context(), token, errTest)
|
||||
fn.ServeHTTP(rr, req.WithContext(ctx))
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
|
@ -1745,19 +1739,10 @@ func TestInvalidClaims(t *testing.T) {
|
|||
form := make(url.Values)
|
||||
form.Set(csrfFormToken, createCSRFToken())
|
||||
form.Set("public_keys", "")
|
||||
req, _ := http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, _ := http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"]))
|
||||
handleWebClientManageKeysPost(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
|
||||
form = make(url.Values)
|
||||
form.Set(csrfFormToken, createCSRFToken())
|
||||
form.Set("allow_api_key_auth", "")
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"]))
|
||||
handleWebClientManageAPIKeyPost(rr, req)
|
||||
handleWebClientProfilePost(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
|
||||
admin := dataprovider.Admin{
|
||||
|
@ -1774,10 +1759,10 @@ func TestInvalidClaims(t *testing.T) {
|
|||
form = make(url.Values)
|
||||
form.Set(csrfFormToken, createCSRFToken())
|
||||
form.Set("allow_api_key_auth", "")
|
||||
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"]))
|
||||
handleWebAdminManageAPIKeyPost(rr, req)
|
||||
handleWebAdminProfilePost(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
}
|
||||
|
||||
|
|
|
@ -243,25 +243,22 @@ paths:
|
|||
$ref: '#/components/responses/InternalServerError'
|
||||
default:
|
||||
$ref: '#/components/responses/DefaultResponse'
|
||||
/admin/apikeyauth:
|
||||
/admin/profile:
|
||||
get:
|
||||
security:
|
||||
- BearerAuth: []
|
||||
tags:
|
||||
- admins
|
||||
summary: Get API key authentication status
|
||||
description: 'Returns the API Key authentication status for the logged in admin'
|
||||
operationId: get_admin_api_key_status
|
||||
summary: Get profile
|
||||
description: 'Returns the profile for the logged in admin'
|
||||
operationId: get_admin_profile
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
allow_api_key_auth:
|
||||
type: boolean
|
||||
$ref: '#/components/schemas/AdminProfile'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
|
@ -275,18 +272,15 @@ paths:
|
|||
- BearerAuth: []
|
||||
tags:
|
||||
- admins
|
||||
summary: Update API key auth status
|
||||
description: 'Allows to enable/disable the API key authentication for the logged in admin. If enabled, you can impersonate this admin, in REST API, using an API key, otherwise your credentials, including two-factor authentication, if enabled, are required to use the REST API on your behalf'
|
||||
operationId: update_admin_api_key_status
|
||||
summary: Update admin profile
|
||||
description: 'Allows to update the profile for the logged in admin'
|
||||
operationId: update_admin_profile
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
allow_api_key_auth:
|
||||
type: boolean
|
||||
$ref: '#/components/schemas/AdminProfile'
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
|
@ -2338,8 +2332,9 @@ paths:
|
|||
- BearerAuth: []
|
||||
tags:
|
||||
- users API
|
||||
deprecated: true
|
||||
summary: Get the user's public keys
|
||||
description: Returns the public keys for the logged in user
|
||||
description: 'Returns the public keys for the logged in user. Deprecated please use "/user/profile" instead'
|
||||
operationId: get_user_public_keys
|
||||
responses:
|
||||
'200':
|
||||
|
@ -2365,8 +2360,9 @@ paths:
|
|||
- BearerAuth: []
|
||||
tags:
|
||||
- users API
|
||||
deprecated: true
|
||||
summary: Set the user's public keys
|
||||
description: Sets the public keys for the logged in user. Public keys must be in OpenSSH format
|
||||
description: 'Sets the public keys for the logged in user. Public keys must be in OpenSSH format. Deprecated please use "/user/profile" instead'
|
||||
operationId: set_user_public_keys
|
||||
requestBody:
|
||||
required: true
|
||||
|
@ -2395,25 +2391,22 @@ paths:
|
|||
$ref: '#/components/responses/InternalServerError'
|
||||
default:
|
||||
$ref: '#/components/responses/DefaultResponse'
|
||||
/user/apikeyauth:
|
||||
/user/profile:
|
||||
get:
|
||||
security:
|
||||
- BearerAuth: []
|
||||
tags:
|
||||
- users API
|
||||
summary: Get API key authentication status
|
||||
description: 'Returns the API Key authentication status for the logged in user'
|
||||
operationId: get_user_api_key_status
|
||||
summary: Get user profile
|
||||
description: 'Returns the profile for the logged in user'
|
||||
operationId: get_user_profile
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
allow_api_key_auth:
|
||||
type: boolean
|
||||
$ref: '#/components/schemas/UserProfile'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
|
@ -2427,18 +2420,15 @@ paths:
|
|||
- BearerAuth: []
|
||||
tags:
|
||||
- users API
|
||||
summary: Update API key auth status
|
||||
description: 'Allows to enable/disable the API key authentication for the logged in user. If enabled, you can impersonate this user, in REST API, using an API key, otherwise your credentials, including two-factor authentication, if enabled, are required to use the REST API on your behalf'
|
||||
operationId: update_user_api_key_status
|
||||
summary: Update profile
|
||||
description: 'Allows to update the profile for the logged in user'
|
||||
operationId: update_user_profile
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
allow_api_key_auth:
|
||||
type: boolean
|
||||
$ref: '#/components/schemas/UserProfile'
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
|
@ -3224,6 +3214,7 @@ components:
|
|||
- mfa-disabled
|
||||
- password-change-disabled
|
||||
- api-key-auth-change-disabled
|
||||
- info-change-disabled
|
||||
description: |
|
||||
Options:
|
||||
* `publickey-change-disabled` - changing SSH public keys is not allowed
|
||||
|
@ -3231,6 +3222,7 @@ components:
|
|||
* `mfa-disabled` - enabling multi-factor authentication is not allowed. This option cannot be set if the user has MFA already enabled
|
||||
* `password-change-disabled` - changing password is not allowed
|
||||
* `api-key-auth-change-disabled` - enabling/disabling API key authentication is not allowed
|
||||
* `info-change-disabled` - changing info such as email and description is not allowed
|
||||
APIKeyScope:
|
||||
type: integer
|
||||
enum:
|
||||
|
@ -3830,6 +3822,34 @@ components:
|
|||
type: integer
|
||||
format: int64
|
||||
description: Last user login as unix timestamp in milliseconds. It is saved at most once every 10 minutes
|
||||
AdminProfile:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
description:
|
||||
type: string
|
||||
allow_api_key_auth:
|
||||
type: boolean
|
||||
description: 'If enabled, you can impersonate this admin, in REST API, using an API key. If disabled admin credentials are required for impersonation'
|
||||
UserProfile:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
description:
|
||||
type: string
|
||||
allow_api_key_auth:
|
||||
type: boolean
|
||||
description: 'If enabled, you can impersonate this user, in REST API, using an API key. If disabled user credentials are required for impersonation'
|
||||
public_keys:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEUWwDwEWhTbF0MqAsp/oXK1HR2cElhM8oo1uVmL3ZeDKDiTm4ljMr92wfTgIGDqIoxmVqgYIkAOAhuykAVWBzc= user@host
|
||||
description: Public keys in OpenSSH format
|
||||
APIKey:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -909,8 +909,8 @@ func (s *httpdServer) initializeRouter() {
|
|||
})
|
||||
|
||||
router.With(forbidAPIKeyAuthentication).Get(logoutPath, s.logout)
|
||||
router.With(forbidAPIKeyAuthentication).Get(adminManageAPIKeyPath, getAdminAPIKeyAuthStatus)
|
||||
router.With(forbidAPIKeyAuthentication).Put(adminManageAPIKeyPath, changeAdminAPIKeyAuthStatus)
|
||||
router.With(forbidAPIKeyAuthentication).Get(adminProfilePath, getAdminProfile)
|
||||
router.With(forbidAPIKeyAuthentication).Put(adminProfilePath, updateAdminProfile)
|
||||
router.With(forbidAPIKeyAuthentication).Put(adminPwdPath, changeAdminPassword)
|
||||
// compatibility layer to remove in v2.2
|
||||
router.With(forbidAPIKeyAuthentication).Put(adminPwdCompatPath, changeAdminPassword)
|
||||
|
@ -1003,9 +1003,8 @@ func (s *httpdServer) initializeRouter() {
|
|||
Get(userPublicKeysPath, getUserPublicKeys)
|
||||
router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)).
|
||||
Put(userPublicKeysPath, setUserPublicKeys)
|
||||
router.With(forbidAPIKeyAuthentication).Get(userManageAPIKeyPath, getUserAPIKeyAuthStatus)
|
||||
router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientAPIKeyAuthChangeDisabled)).
|
||||
Put(userManageAPIKeyPath, changeUserAPIKeyAuthStatus)
|
||||
router.With(forbidAPIKeyAuthentication).Get(userProfilePath, getUserProfile)
|
||||
router.With(forbidAPIKeyAuthentication).Put(userProfilePath, updateUserProfile)
|
||||
// user TOTP APIs
|
||||
router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientMFADisabled)).
|
||||
Get(userTOTPConfigsPath, getTOTPConfigs)
|
||||
|
@ -1101,13 +1100,12 @@ func (s *httpdServer) initializeRouter() {
|
|||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader).
|
||||
Delete(webClientDirsPath, deleteUserDir)
|
||||
router.With(s.refreshCookie).Get(webClientDownloadZipPath, handleWebClientDownloadZip)
|
||||
router.With(s.refreshCookie).Get(webClientCredentialsPath, handleClientGetCredentials)
|
||||
router.With(s.refreshCookie).Get(webClientProfilePath, handleClientGetProfile)
|
||||
router.Post(webClientProfilePath, handleWebClientProfilePost)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)).
|
||||
Get(webChangeClientPwdPath, handleWebClientChangePwd)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)).
|
||||
Post(webChangeClientPwdPath, handleWebClientChangePwdPost)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientAPIKeyAuthChangeDisabled)).
|
||||
Post(webChangeClientAPIKeyAccessPath, handleWebClientManageAPIKeyPost)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)).
|
||||
Post(webChangeClientKeysPath, handleWebClientManageKeysPost)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientMFADisabled), s.refreshCookie).
|
||||
Get(webClientMFAPath, handleWebClientMFA)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader).
|
||||
|
@ -1150,9 +1148,11 @@ func (s *httpdServer) initializeRouter() {
|
|||
router.Use(jwtAuthenticatorWebAdmin)
|
||||
|
||||
router.Get(webLogoutPath, handleWebLogout)
|
||||
router.With(s.refreshCookie).Get(webAdminCredentialsPath, handleWebAdminCredentials)
|
||||
router.With(s.refreshCookie).Get(webAdminProfilePath, handleWebAdminProfile)
|
||||
router.Post(webAdminProfilePath, handleWebAdminProfilePost)
|
||||
router.With(s.refreshCookie).Get(webChangeAdminPwdPath, handleWebAdminChangePwd)
|
||||
router.Post(webChangeAdminPwdPath, handleWebAdminChangePwdPost)
|
||||
router.Post(webChangeAdminAPIKeyAccessPath, handleWebAdminManageAPIKeyPost)
|
||||
|
||||
router.With(s.refreshCookie).Get(webAdminMFAPath, handleWebAdminMFA)
|
||||
router.With(verifyCSRFHeader).Post(webAdminTOTPGeneratePath, generateTOTPSecret)
|
||||
router.With(verifyCSRFHeader).Post(webAdminTOTPValidatePath, validateTOTPPasscode)
|
||||
|
|
|
@ -56,7 +56,8 @@ const (
|
|||
templateStatus = "status.html"
|
||||
templateLogin = "login.html"
|
||||
templateDefender = "defender.html"
|
||||
templateCredentials = "credentials.html"
|
||||
templateProfile = "profile.html"
|
||||
templateChangePwd = "changepassword.html"
|
||||
templateMaintenance = "maintenance.html"
|
||||
templateMFA = "mfa.html"
|
||||
templateSetup = "adminsetup.html"
|
||||
|
@ -65,7 +66,8 @@ const (
|
|||
pageConnectionsTitle = "Connections"
|
||||
pageStatusTitle = "Status"
|
||||
pageFoldersTitle = "Folders"
|
||||
pageCredentialsTitle = "Manage credentials"
|
||||
pageProfileTitle = "My profile"
|
||||
pageChangePwdTitle = "Change password"
|
||||
pageMaintenanceTitle = "Maintenance"
|
||||
pageDefenderTitle = "Defender"
|
||||
pageSetupTitle = "Create first admin user"
|
||||
|
@ -91,7 +93,8 @@ type basePage struct {
|
|||
FolderTemplateURL string
|
||||
DefenderURL string
|
||||
LogoutURL string
|
||||
CredentialsURL string
|
||||
ProfileURL string
|
||||
ChangePwdURL string
|
||||
MFAURL string
|
||||
FolderQuotaScanURL string
|
||||
StatusURL string
|
||||
|
@ -157,13 +160,17 @@ type adminPage struct {
|
|||
IsAdd bool
|
||||
}
|
||||
|
||||
type credentialsPage struct {
|
||||
type profilePage struct {
|
||||
basePage
|
||||
Error string
|
||||
AllowAPIKeyAuth bool
|
||||
ChangePwdURL string
|
||||
ManageAPIKeyURL string
|
||||
APIKeyError string
|
||||
Email string
|
||||
Description string
|
||||
}
|
||||
|
||||
type changePasswordPage struct {
|
||||
basePage
|
||||
Error string
|
||||
}
|
||||
|
||||
type mfaPage struct {
|
||||
|
@ -231,9 +238,13 @@ func loadAdminTemplates(templatesPath string) {
|
|||
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateAdmin),
|
||||
}
|
||||
credentialsPaths := []string{
|
||||
profilePaths := []string{
|
||||
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateCredentials),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateProfile),
|
||||
}
|
||||
changePwdPaths := []string{
|
||||
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateChangePwd),
|
||||
}
|
||||
connectionsPaths := []string{
|
||||
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
||||
|
@ -298,7 +309,8 @@ func loadAdminTemplates(templatesPath string) {
|
|||
folderTmpl := util.LoadTemplate(rootTpl, folderPath...)
|
||||
statusTmpl := util.LoadTemplate(rootTpl, statusPath...)
|
||||
loginTmpl := util.LoadTemplate(rootTpl, loginPath...)
|
||||
credentialsTmpl := util.LoadTemplate(rootTpl, credentialsPaths...)
|
||||
profileTmpl := util.LoadTemplate(rootTpl, profilePaths...)
|
||||
changePwdTmpl := util.LoadTemplate(rootTpl, changePwdPaths...)
|
||||
maintenanceTmpl := util.LoadTemplate(rootTpl, maintenancePath...)
|
||||
defenderTmpl := util.LoadTemplate(rootTpl, defenderPath...)
|
||||
mfaTmpl := util.LoadTemplate(nil, mfaPath...)
|
||||
|
@ -316,7 +328,8 @@ func loadAdminTemplates(templatesPath string) {
|
|||
adminTemplates[templateFolder] = folderTmpl
|
||||
adminTemplates[templateStatus] = statusTmpl
|
||||
adminTemplates[templateLogin] = loginTmpl
|
||||
adminTemplates[templateCredentials] = credentialsTmpl
|
||||
adminTemplates[templateProfile] = profileTmpl
|
||||
adminTemplates[templateChangePwd] = changePwdTmpl
|
||||
adminTemplates[templateMaintenance] = maintenanceTmpl
|
||||
adminTemplates[templateDefender] = defenderTmpl
|
||||
adminTemplates[templateMFA] = mfaTmpl
|
||||
|
@ -343,7 +356,8 @@ func getBasePageData(title, currentURL string, r *http.Request) basePage {
|
|||
FolderTemplateURL: webTemplateFolder,
|
||||
DefenderURL: webDefenderPath,
|
||||
LogoutURL: webLogoutPath,
|
||||
CredentialsURL: webAdminCredentialsPath,
|
||||
ProfileURL: webAdminProfilePath,
|
||||
ChangePwdURL: webChangeAdminPwdPath,
|
||||
MFAURL: webAdminMFAPath,
|
||||
QuotaScanURL: webQuotaScanPath,
|
||||
ConnectionsURL: webConnectionsPath,
|
||||
|
@ -446,13 +460,10 @@ func renderMFAPage(w http.ResponseWriter, r *http.Request) {
|
|||
renderAdminTemplate(w, templateMFA, data)
|
||||
}
|
||||
|
||||
func renderCredentialsPage(w http.ResponseWriter, r *http.Request, pwdError, apiKeyError string) {
|
||||
data := credentialsPage{
|
||||
basePage: getBasePageData(pageCredentialsTitle, webAdminCredentialsPath, r),
|
||||
ChangePwdURL: webChangeAdminPwdPath,
|
||||
ManageAPIKeyURL: webChangeAdminAPIKeyAccessPath,
|
||||
Error: pwdError,
|
||||
APIKeyError: apiKeyError,
|
||||
func renderProfilePage(w http.ResponseWriter, r *http.Request, error string) {
|
||||
data := profilePage{
|
||||
basePage: getBasePageData(pageProfileTitle, webAdminProfilePath, r),
|
||||
Error: error,
|
||||
}
|
||||
admin, err := dataprovider.AdminExists(data.LoggedAdmin.Username)
|
||||
if err != nil {
|
||||
|
@ -460,8 +471,19 @@ func renderCredentialsPage(w http.ResponseWriter, r *http.Request, pwdError, api
|
|||
return
|
||||
}
|
||||
data.AllowAPIKeyAuth = admin.Filters.AllowAPIKeyAuth
|
||||
data.Email = admin.Email
|
||||
data.Description = admin.Description
|
||||
|
||||
renderAdminTemplate(w, templateCredentials, data)
|
||||
renderAdminTemplate(w, templateProfile, data)
|
||||
}
|
||||
|
||||
func renderChangePasswordPage(w http.ResponseWriter, r *http.Request, error string) {
|
||||
data := changePasswordPage{
|
||||
basePage: getBasePageData(pageChangePwdTitle, webChangeAdminPwdPath, r),
|
||||
Error: error,
|
||||
}
|
||||
|
||||
renderAdminTemplate(w, templateChangePwd, data)
|
||||
}
|
||||
|
||||
func renderMaintenancePage(w http.ResponseWriter, r *http.Request, error string) {
|
||||
|
@ -1125,16 +1147,21 @@ func handleWebAdminMFA(w http.ResponseWriter, r *http.Request) {
|
|||
renderMFAPage(w, r)
|
||||
}
|
||||
|
||||
func handleWebAdminCredentials(w http.ResponseWriter, r *http.Request) {
|
||||
func handleWebAdminProfile(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
renderCredentialsPage(w, r, "", "")
|
||||
renderProfilePage(w, r, "")
|
||||
}
|
||||
|
||||
func handleWebAdminChangePwd(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
renderChangePasswordPage(w, r, "")
|
||||
}
|
||||
|
||||
func handleWebAdminChangePwdPost(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
renderCredentialsPage(w, r, err.Error(), "")
|
||||
renderChangePasswordPage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
||||
|
@ -1144,17 +1171,17 @@ func handleWebAdminChangePwdPost(w http.ResponseWriter, r *http.Request) {
|
|||
err = doChangeAdminPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"),
|
||||
r.Form.Get("new_password2"))
|
||||
if err != nil {
|
||||
renderCredentialsPage(w, r, err.Error(), "")
|
||||
renderChangePasswordPage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
handleWebLogout(w, r)
|
||||
}
|
||||
|
||||
func handleWebAdminManageAPIKeyPost(w http.ResponseWriter, r *http.Request) {
|
||||
func handleWebAdminProfilePost(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
renderCredentialsPage(w, r, err.Error(), "")
|
||||
renderProfilePage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
||||
|
@ -1163,22 +1190,24 @@ func handleWebAdminManageAPIKeyPost(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
claims, err := getTokenClaims(r)
|
||||
if err != nil || claims.Username == "" {
|
||||
renderCredentialsPage(w, r, "", "Invalid token claims")
|
||||
renderProfilePage(w, r, "Invalid token claims")
|
||||
return
|
||||
}
|
||||
admin, err := dataprovider.AdminExists(claims.Username)
|
||||
if err != nil {
|
||||
renderCredentialsPage(w, r, "", err.Error())
|
||||
renderProfilePage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
admin.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
|
||||
admin.Email = r.Form.Get("email")
|
||||
admin.Description = r.Form.Get("description")
|
||||
err = dataprovider.UpdateAdmin(&admin)
|
||||
if err != nil {
|
||||
renderCredentialsPage(w, r, "", err.Error())
|
||||
renderProfilePage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
renderMessagePage(w, r, "API key authentication updated", "", http.StatusOK, nil,
|
||||
"Your API key access permission has been successfully updated")
|
||||
renderMessagePage(w, r, "Profile updated", "", http.StatusOK, nil,
|
||||
"Your profile has been successfully updated")
|
||||
}
|
||||
|
||||
func handleWebLogout(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -30,12 +30,15 @@ const (
|
|||
templateClientLogin = "login.html"
|
||||
templateClientFiles = "files.html"
|
||||
templateClientMessage = "message.html"
|
||||
templateClientCredentials = "credentials.html"
|
||||
templateClientProfile = "profile.html"
|
||||
templateClientChangePwd = "changepassword.html"
|
||||
templateClientTwoFactor = "twofactor.html"
|
||||
templateClientTwoFactorRecovery = "twofactor-recovery.html"
|
||||
templateClientMFA = "mfa.html"
|
||||
pageClientFilesTitle = "My Files"
|
||||
pageClientCredentialsTitle = "Credentials"
|
||||
pageClientProfileTitle = "My Profile"
|
||||
pageClientChangePwdTitle = "Change password"
|
||||
pageClient2FATitle = "Two-factor auth"
|
||||
)
|
||||
|
||||
// condResult is the result of an HTTP request precondition check.
|
||||
|
@ -59,19 +62,20 @@ func isZeroTime(t time.Time) bool {
|
|||
}
|
||||
|
||||
type baseClientPage struct {
|
||||
Title string
|
||||
CurrentURL string
|
||||
FilesURL string
|
||||
CredentialsURL string
|
||||
StaticURL string
|
||||
LogoutURL string
|
||||
MFAURL string
|
||||
MFATitle string
|
||||
FilesTitle string
|
||||
CredentialsTitle string
|
||||
Version string
|
||||
CSRFToken string
|
||||
LoggedUser *dataprovider.User
|
||||
Title string
|
||||
CurrentURL string
|
||||
FilesURL string
|
||||
ProfileURL string
|
||||
ChangePwdURL string
|
||||
StaticURL string
|
||||
LogoutURL string
|
||||
MFAURL string
|
||||
MFATitle string
|
||||
FilesTitle string
|
||||
ProfileTitle string
|
||||
Version string
|
||||
CSRFToken string
|
||||
LoggedUser *dataprovider.User
|
||||
}
|
||||
|
||||
type dirMapping struct {
|
||||
|
@ -99,16 +103,19 @@ type clientMessagePage struct {
|
|||
Success string
|
||||
}
|
||||
|
||||
type clientCredentialsPage struct {
|
||||
type clientProfilePage struct {
|
||||
baseClientPage
|
||||
PublicKeys []string
|
||||
CanSubmit bool
|
||||
AllowAPIKeyAuth bool
|
||||
ChangePwdURL string
|
||||
ManageKeysURL string
|
||||
ManageAPIKeyURL string
|
||||
PwdError string
|
||||
KeyError string
|
||||
APIKeyError string
|
||||
Email string
|
||||
Description string
|
||||
Error string
|
||||
}
|
||||
|
||||
type changeClientPasswordPage struct {
|
||||
baseClientPage
|
||||
Error string
|
||||
}
|
||||
|
||||
type clientMFAPage struct {
|
||||
|
@ -138,9 +145,13 @@ func loadClientTemplates(templatesPath string) {
|
|||
filepath.Join(templatesPath, templateClientDir, templateClientBase),
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientFiles),
|
||||
}
|
||||
credentialsPaths := []string{
|
||||
profilePaths := []string{
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientBase),
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientCredentials),
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientProfile),
|
||||
}
|
||||
changePwdPaths := []string{
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientBase),
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientChangePwd),
|
||||
}
|
||||
loginPath := []string{
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientBaseLogin),
|
||||
|
@ -164,7 +175,8 @@ func loadClientTemplates(templatesPath string) {
|
|||
}
|
||||
|
||||
filesTmpl := util.LoadTemplate(nil, filesPaths...)
|
||||
credentialsTmpl := util.LoadTemplate(nil, credentialsPaths...)
|
||||
profileTmpl := util.LoadTemplate(nil, profilePaths...)
|
||||
changePwdTmpl := util.LoadTemplate(nil, changePwdPaths...)
|
||||
loginTmpl := util.LoadTemplate(nil, loginPath...)
|
||||
messageTmpl := util.LoadTemplate(nil, messagePath...)
|
||||
mfaTmpl := util.LoadTemplate(nil, mfaPath...)
|
||||
|
@ -172,7 +184,8 @@ func loadClientTemplates(templatesPath string) {
|
|||
twoFactorRecoveryTmpl := util.LoadTemplate(nil, twoFactorRecoveryPath...)
|
||||
|
||||
clientTemplates[templateClientFiles] = filesTmpl
|
||||
clientTemplates[templateClientCredentials] = credentialsTmpl
|
||||
clientTemplates[templateClientProfile] = profileTmpl
|
||||
clientTemplates[templateClientChangePwd] = changePwdTmpl
|
||||
clientTemplates[templateClientLogin] = loginTmpl
|
||||
clientTemplates[templateClientMessage] = messageTmpl
|
||||
clientTemplates[templateClientMFA] = mfaTmpl
|
||||
|
@ -188,19 +201,20 @@ func getBaseClientPageData(title, currentURL string, r *http.Request) baseClient
|
|||
v := version.Get()
|
||||
|
||||
return baseClientPage{
|
||||
Title: title,
|
||||
CurrentURL: currentURL,
|
||||
FilesURL: webClientFilesPath,
|
||||
CredentialsURL: webClientCredentialsPath,
|
||||
StaticURL: webStaticFilesPath,
|
||||
LogoutURL: webClientLogoutPath,
|
||||
MFAURL: webClientMFAPath,
|
||||
MFATitle: "Two-factor auth",
|
||||
FilesTitle: pageClientFilesTitle,
|
||||
CredentialsTitle: pageClientCredentialsTitle,
|
||||
Version: fmt.Sprintf("%v-%v", v.Version, v.CommitHash),
|
||||
CSRFToken: csrfToken,
|
||||
LoggedUser: getUserFromToken(r),
|
||||
Title: title,
|
||||
CurrentURL: currentURL,
|
||||
FilesURL: webClientFilesPath,
|
||||
ProfileURL: webClientProfilePath,
|
||||
ChangePwdURL: webChangeClientPwdPath,
|
||||
StaticURL: webStaticFilesPath,
|
||||
LogoutURL: webClientLogoutPath,
|
||||
MFAURL: webClientMFAPath,
|
||||
MFATitle: pageClient2FATitle,
|
||||
FilesTitle: pageClientFilesTitle,
|
||||
ProfileTitle: pageClientProfileTitle,
|
||||
Version: fmt.Sprintf("%v-%v", v.Version, v.CommitHash),
|
||||
CSRFToken: csrfToken,
|
||||
LoggedUser: getUserFromToken(r),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,15 +334,10 @@ func renderFilesPage(w http.ResponseWriter, r *http.Request, dirName, error stri
|
|||
renderClientTemplate(w, templateClientFiles, data)
|
||||
}
|
||||
|
||||
func renderClientCredentialsPage(w http.ResponseWriter, r *http.Request, pwdError, keyError, apiKeyError string) {
|
||||
data := clientCredentialsPage{
|
||||
baseClientPage: getBaseClientPageData(pageClientCredentialsTitle, webClientCredentialsPath, r),
|
||||
ChangePwdURL: webChangeClientPwdPath,
|
||||
ManageKeysURL: webChangeClientKeysPath,
|
||||
ManageAPIKeyURL: webChangeClientAPIKeyAccessPath,
|
||||
PwdError: pwdError,
|
||||
KeyError: keyError,
|
||||
APIKeyError: apiKeyError,
|
||||
func renderClientProfilePage(w http.ResponseWriter, r *http.Request, error string) {
|
||||
data := clientProfilePage{
|
||||
baseClientPage: getBaseClientPageData(pageClientProfileTitle, webClientProfilePath, r),
|
||||
Error: error,
|
||||
}
|
||||
user, err := dataprovider.UserExists(data.LoggedUser.Username)
|
||||
if err != nil {
|
||||
|
@ -337,7 +346,19 @@ func renderClientCredentialsPage(w http.ResponseWriter, r *http.Request, pwdErro
|
|||
}
|
||||
data.PublicKeys = user.PublicKeys
|
||||
data.AllowAPIKeyAuth = user.Filters.AllowAPIKeyAuth
|
||||
renderClientTemplate(w, templateClientCredentials, data)
|
||||
data.Email = user.Email
|
||||
data.Description = user.Description
|
||||
data.CanSubmit = user.CanChangeAPIKeyAuth() || user.CanManagePublicKeys() || user.CanChangeInfo()
|
||||
renderClientTemplate(w, templateClientProfile, data)
|
||||
}
|
||||
|
||||
func renderClientChangePasswordPage(w http.ResponseWriter, r *http.Request, error string) {
|
||||
data := changeClientPasswordPage{
|
||||
baseClientPage: getBaseClientPageData(pageClientChangePwdTitle, webChangeClientPwdPath, r),
|
||||
Error: error,
|
||||
}
|
||||
|
||||
renderClientTemplate(w, templateClientChangePwd, data)
|
||||
}
|
||||
|
||||
func handleWebClientLogout(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -513,16 +534,21 @@ func handleClientGetFiles(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func handleClientGetCredentials(w http.ResponseWriter, r *http.Request) {
|
||||
func handleClientGetProfile(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
renderClientCredentialsPage(w, r, "", "", "")
|
||||
renderClientProfilePage(w, r, "")
|
||||
}
|
||||
|
||||
func handleWebClientChangePwd(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
renderClientChangePasswordPage(w, r, "")
|
||||
}
|
||||
|
||||
func handleWebClientChangePwdPost(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
renderClientCredentialsPage(w, r, err.Error(), "", "")
|
||||
renderClientChangePasswordPage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
||||
|
@ -532,17 +558,17 @@ func handleWebClientChangePwdPost(w http.ResponseWriter, r *http.Request) {
|
|||
err = doChangeUserPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"),
|
||||
r.Form.Get("new_password2"))
|
||||
if err != nil {
|
||||
renderClientCredentialsPage(w, r, err.Error(), "", "")
|
||||
renderClientChangePasswordPage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
handleWebClientLogout(w, r)
|
||||
}
|
||||
|
||||
func handleWebClientManageKeysPost(w http.ResponseWriter, r *http.Request) {
|
||||
func handleWebClientProfilePost(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
renderClientCredentialsPage(w, r, "", err.Error(), "")
|
||||
renderClientProfilePage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
||||
|
@ -551,53 +577,35 @@ func handleWebClientManageKeysPost(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
claims, err := getTokenClaims(r)
|
||||
if err != nil || claims.Username == "" {
|
||||
renderClientCredentialsPage(w, r, "", "Invalid token claims", "")
|
||||
renderClientProfilePage(w, r, "Invalid token claims")
|
||||
return
|
||||
}
|
||||
user, err := dataprovider.UserExists(claims.Username)
|
||||
if err != nil {
|
||||
renderClientCredentialsPage(w, r, "", err.Error(), "")
|
||||
renderClientProfilePage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
user.PublicKeys = r.Form["public_keys"]
|
||||
if !user.CanManagePublicKeys() && !user.CanChangeAPIKeyAuth() && !user.CanChangeInfo() {
|
||||
renderClientForbiddenPage(w, r, "You are not allowed to change anything")
|
||||
return
|
||||
}
|
||||
if user.CanManagePublicKeys() {
|
||||
user.PublicKeys = r.Form["public_keys"]
|
||||
}
|
||||
if user.CanChangeAPIKeyAuth() {
|
||||
user.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
|
||||
}
|
||||
if user.CanChangeInfo() {
|
||||
user.Email = r.Form.Get("email")
|
||||
user.Description = r.Form.Get("description")
|
||||
}
|
||||
err = dataprovider.UpdateUser(&user)
|
||||
if err != nil {
|
||||
renderClientCredentialsPage(w, r, "", err.Error(), "")
|
||||
renderClientProfilePage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
renderClientMessagePage(w, r, "Public keys updated", "", http.StatusOK, nil,
|
||||
"Your public keys has been successfully updated")
|
||||
}
|
||||
|
||||
func handleWebClientManageAPIKeyPost(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
renderClientCredentialsPage(w, r, "", "", err.Error())
|
||||
return
|
||||
}
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
||||
renderClientForbiddenPage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
claims, err := getTokenClaims(r)
|
||||
if err != nil || claims.Username == "" {
|
||||
renderClientCredentialsPage(w, r, "", "", "Invalid token claims")
|
||||
return
|
||||
}
|
||||
user, err := dataprovider.UserExists(claims.Username)
|
||||
if err != nil {
|
||||
renderClientCredentialsPage(w, r, "", "", err.Error())
|
||||
return
|
||||
}
|
||||
user.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
|
||||
err = dataprovider.UpdateUser(&user)
|
||||
if err != nil {
|
||||
renderClientCredentialsPage(w, r, "", "", err.Error())
|
||||
return
|
||||
}
|
||||
renderClientMessagePage(w, r, "API key authentication updated", "", http.StatusOK, nil,
|
||||
"Your API key access permission has been successfully updated")
|
||||
renderClientMessagePage(w, r, "Profile updated", "", http.StatusOK, nil,
|
||||
"Your profile has been successfully updated")
|
||||
}
|
||||
|
||||
func handleWebClientMFA(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -14,12 +14,13 @@ const (
|
|||
WebClientMFADisabled = "mfa-disabled"
|
||||
WebClientPasswordChangeDisabled = "password-change-disabled"
|
||||
WebClientAPIKeyAuthChangeDisabled = "api-key-auth-change-disabled"
|
||||
WebClientInfoChangeDisabled = "info-change-disabled"
|
||||
)
|
||||
|
||||
var (
|
||||
// WebClientOptions defines the available options for the web client interface/user REST API
|
||||
WebClientOptions = []string{WebClientPubKeyChangeDisabled, WebClientWriteDisabled, WebClientMFADisabled,
|
||||
WebClientPasswordChangeDisabled, WebClientAPIKeyAuthChangeDisabled}
|
||||
WebClientOptions = []string{WebClientWriteDisabled, WebClientPasswordChangeDisabled, WebClientPubKeyChangeDisabled,
|
||||
WebClientMFADisabled, WebClientAPIKeyAuthChangeDisabled, WebClientInfoChangeDisabled}
|
||||
// UserTypes defines the supported user type hints for auth plugins
|
||||
UserTypes = []string{string(UserTypeLDAP), string(UserTypeOS)}
|
||||
)
|
||||
|
|
|
@ -115,7 +115,6 @@ func (c *Config) getAuthType() mail.AuthType {
|
|||
}
|
||||
|
||||
// SendEmail tries to send an email using the specified parameters.
|
||||
// If the contentType is 0 text/plain is assumed, otherwise text/html
|
||||
func SendEmail(to, subject, body string, contentType EmailContentType) error {
|
||||
if smtpServer == nil {
|
||||
return errors.New("smtp: not configured")
|
||||
|
|
|
@ -22,6 +22,14 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idEmail" class="col-sm-2 col-form-label">Email</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="idEmail" name="email" placeholder=""
|
||||
value="{{.Admin.Email}}" maxlength="255">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idDescription" class="col-sm-2 col-form-label">Description</label>
|
||||
<div class="col-sm-10">
|
||||
|
@ -69,14 +77,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idEmail" class="col-sm-2 col-form-label">Email</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="idEmail" name="email" placeholder=""
|
||||
value="{{.Admin.Email}}" maxlength="255">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idAllowedIP" class="col-sm-2 col-form-label">Allowed IP/Mask</label>
|
||||
<div class="col-sm-10">
|
||||
|
|
|
@ -167,9 +167,13 @@
|
|||
</a>
|
||||
<!-- Dropdown - User Information -->
|
||||
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown">
|
||||
<a class="dropdown-item" href="{{.CredentialsURL}}">
|
||||
<a class="dropdown-item" href="{{.ProfileURL}}">
|
||||
<i class="fas fa-user fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Profile
|
||||
</a>
|
||||
<a class="dropdown-item" href="{{.ChangePwdURL}}">
|
||||
<i class="fas fa-key fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Credentials
|
||||
Change password
|
||||
</a>
|
||||
{{if .LoggedAdmin.CanManageMFA}}
|
||||
<a class="dropdown-item" href="{{.MFAURL}}">
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div class="card-body text-form-error">{{.Error}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="user_form" action="{{.ChangePwdURL}}" method="POST" autocomplete="off">
|
||||
<form id="user_form" action="{{.CurrentURL}}" method="POST" autocomplete="off">
|
||||
<div class="form-group row">
|
||||
<label for="idCurrentPassword" class="col-sm-2 col-form-label">Current password</label>
|
||||
<div class="col-sm-10">
|
||||
|
@ -41,31 +41,4 @@
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">REST API access</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .APIKeyError}}
|
||||
<div class="card mb-4 border-left-warning">
|
||||
<div class="card-body text-form-error">{{.APIKeyError}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="key_form" action="{{.ManageAPIKeyURL}}" method="POST">
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="idAllowAPIKeyAuth" name="allow_api_key_auth"
|
||||
{{if .AllowAPIKeyAuth}}checked{{end}} aria-describedby="allowAPIKeyAuthHelpBlock">
|
||||
<label for="idAllowAPIKeyAuth" class="form-check-label">Allow API key authentication</label>
|
||||
<small id="allowAPIKeyAuthHelpBlock" class="form-text text-muted">
|
||||
Allow to impersonate yourself, in REST API, with an API key. If this permission is not granted, your credentials are required to use the REST API on your behalf
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
50
templates/webadmin/profile.html
Normal file
50
templates/webadmin/profile.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
{{template "base" .}}
|
||||
|
||||
{{define "title"}}{{.Title}}{{end}}
|
||||
|
||||
{{define "page_body"}}
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">My profile - {{.LoggedAdmin.Username}}</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .Error}}
|
||||
<div class="card mb-4 border-left-warning">
|
||||
<div class="card-body text-form-error">{{.Error}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="profile_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="idEmail" class="col-sm-2 col-form-label">Email</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="idEmail" name="email" placeholder=""
|
||||
value="{{.Email}}" maxlength="255">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idDescription" class="col-sm-2 col-form-label">Description</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="idDescription" name="description" placeholder=""
|
||||
value="{{.Description}}" maxlength="255">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="idAllowAPIKeyAuth" name="allow_api_key_auth"
|
||||
{{if .AllowAPIKeyAuth}}checked{{end}} aria-describedby="allowAPIKeyAuthHelpBlock">
|
||||
<label for="idAllowAPIKeyAuth" class="form-check-label">Allow API key authentication</label>
|
||||
<small id="allowAPIKeyAuthHelpBlock" class="form-text text-muted">
|
||||
Allow to impersonate yourself, in REST API, with an API key. If this permission is not granted, your credentials are required to use the REST API on your behalf
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -79,10 +79,10 @@
|
|||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item {{if eq .CurrentURL .CredentialsURL}}active{{end}}">
|
||||
<a class="nav-link" href="{{.CredentialsURL}}">
|
||||
<i class="fas fa-key"></i>
|
||||
<span>{{.CredentialsTitle}}</span></a>
|
||||
<li class="nav-item {{if eq .CurrentURL .ProfileURL}}active{{end}}">
|
||||
<a class="nav-link" href="{{.ProfileURL}}">
|
||||
<i class="fas fa-user"></i>
|
||||
<span>{{.ProfileTitle}}</span></a>
|
||||
</li>
|
||||
{{if .LoggedUser.CanManageMFA}}
|
||||
<li class="nav-item {{if eq .CurrentURL .MFAURL}}active{{end}}">
|
||||
|
@ -129,6 +129,13 @@
|
|||
</a>
|
||||
<!-- Dropdown - User Information -->
|
||||
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown">
|
||||
{{if .LoggedUser.CanChangePassword}}
|
||||
<a class="dropdown-item" href="{{.ChangePwdURL}}">
|
||||
<i class="fas fa-key fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Change password
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
{{end}}
|
||||
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal">
|
||||
<i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Logout
|
||||
|
|
44
templates/webclient/changepassword.html
Normal file
44
templates/webclient/changepassword.html
Normal file
|
@ -0,0 +1,44 @@
|
|||
{{template "base" .}}
|
||||
|
||||
{{define "title"}}{{.Title}}{{end}}
|
||||
|
||||
{{define "page_body"}}
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Change password</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .Error}}
|
||||
<div class="card mb-4 border-left-warning">
|
||||
<div class="card-body text-form-error">{{.Error}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="user_form" action="{{.CurrentURL}}" method="POST" autocomplete="off">
|
||||
<div class="form-group row">
|
||||
<label for="idCurrentPassword" class="col-sm-2 col-form-label">Current password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idCurrentPassword" name="current_password" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idNewPassword1" class="col-sm-2 col-form-label">New password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idNewPassword1" name="new_password1" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idNewPassword2" class="col-sm-2 col-form-label">Confirm password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idNewPassword2" name="new_password2" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Change my password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -1,158 +0,0 @@
|
|||
{{template "base" .}}
|
||||
|
||||
{{define "title"}}{{.Title}}{{end}}
|
||||
|
||||
{{define "page_body"}}
|
||||
|
||||
{{if .LoggedUser.CanChangePassword}}
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Change password</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .PwdError}}
|
||||
<div class="card mb-4 border-left-warning">
|
||||
<div class="card-body text-form-error">{{.PwdError}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="user_form" action="{{.ChangePwdURL}}" method="POST" autocomplete="off">
|
||||
<div class="form-group row">
|
||||
<label for="idCurrentPassword" class="col-sm-2 col-form-label">Current password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idCurrentPassword" name="current_password" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idNewPassword1" class="col-sm-2 col-form-label">New password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idNewPassword1" name="new_password1" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idNewPassword2" class="col-sm-2 col-form-label">Confirm password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idNewPassword2" name="new_password2" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Change my password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .LoggedUser.CanManagePublicKeys}}
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Manage public keys</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .KeyError}}
|
||||
<div class="card mb-4 border-left-warning">
|
||||
<div class="card-body text-form-error">{{.KeyError}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="key_form" action="{{.ManageKeysURL}}" method="POST">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12 form_field_pk_outer">
|
||||
{{range $idx, $val := .PublicKeys}}
|
||||
<div class="row form_field_pk_outer_row">
|
||||
<div class="form-group col-md-11">
|
||||
<textarea class="form-control" id="idPublicKey{{$idx}}" name="public_keys" rows="4"
|
||||
placeholder="Paste your public key here">{{$val}}</textarea>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="row form_field_pk_outer_row">
|
||||
<div class="form-group col-md-11">
|
||||
<textarea class="form-control" id="idPublicKey0" name="public_keys" rows="4"
|
||||
placeholder="Paste your public key here"></textarea>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field" disabled>
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mx-1">
|
||||
<button type="button" class="btn btn-secondary add_new_pk_field_btn">
|
||||
<i class="fas fa-plus"></i> Add new public key
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">REST API access</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .APIKeyError}}
|
||||
<div class="card mb-4 border-left-warning">
|
||||
<div class="card-body text-form-error">{{.APIKeyError}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="key_form" action="{{.ManageAPIKeyURL}}" method="POST">
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="idAllowAPIKeyAuth" name="allow_api_key_auth" {{if not .LoggedUser.CanChangeAPIKeyAuth}}disabled="disabled"{{end}}
|
||||
{{if .AllowAPIKeyAuth}}checked{{end}} aria-describedby="allowAPIKeyAuthHelpBlock">
|
||||
<label for="idAllowAPIKeyAuth" class="form-check-label">Allow API key authentication</label>
|
||||
<small id="allowAPIKeyAuthHelpBlock" class="form-text text-muted">
|
||||
Allow to impersonate yourself, in REST API, with an API key. If this permission is not granted, your credentials are required to use the REST API on your behalf
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
{{if .LoggedUser.CanChangeAPIKeyAuth}}
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
|
||||
{{end}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "extra_js"}}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$("body").on("click", ".add_new_pk_field_btn", function () {
|
||||
var index = $(".form_field_pk_outer").find(".form_field_pk_outer_row").length;
|
||||
while (document.getElementById("idPublicKey"+index) != null){
|
||||
index++;
|
||||
}
|
||||
$(".form_field_pk_outer").append(`
|
||||
<div class="row form_field_pk_outer_row">
|
||||
<div class="form-group col-md-11">
|
||||
<textarea class="form-control" id="idPublicKey${index}" name="public_keys" rows="4"
|
||||
placeholder="Paste your public key here"></textarea>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
||||
$("body").on("click", ".remove_pk_btn_frm_field", function () {
|
||||
$(this).closest(".form_field_pk_outer_row").remove();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
128
templates/webclient/profile.html
Normal file
128
templates/webclient/profile.html
Normal file
|
@ -0,0 +1,128 @@
|
|||
{{template "base" .}}
|
||||
|
||||
{{define "title"}}{{.Title}}{{end}}
|
||||
|
||||
{{define "page_body"}}
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">My profile - {{.LoggedUser.Username}}</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .Error}}
|
||||
<div class="card mb-4 border-left-warning">
|
||||
<div class="card-body text-form-error">{{.Error}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="profile_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="idEmail" class="col-sm-2 col-form-label">Email</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="idEmail" name="email" placeholder=""
|
||||
value="{{.Email}}" maxlength="255" autocomplete="nope" {{if not .LoggedUser.CanChangeInfo}}readonly{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idDescription" class="col-sm-2 col-form-label">Description</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="idDescription" name="description" placeholder=""
|
||||
value="{{.Description}}" maxlength="255" {{if not .LoggedUser.CanChangeInfo}}readonly{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="idAllowAPIKeyAuth" name="allow_api_key_auth" {{if not .LoggedUser.CanChangeAPIKeyAuth}}disabled="disabled"{{end}}
|
||||
{{if .AllowAPIKeyAuth}}checked{{end}} aria-describedby="allowAPIKeyAuthHelpBlock">
|
||||
<label for="idAllowAPIKeyAuth" class="form-check-label">Allow API key authentication</label>
|
||||
<small id="allowAPIKeyAuthHelpBlock" class="form-text text-muted">
|
||||
Allow to impersonate yourself, in REST API, with an API key. If this permission is not granted, your credentials are required to use the REST API on your behalf
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .LoggedUser.CanManagePublicKeys}}
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-header">
|
||||
Public keys
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12 form_field_pk_outer">
|
||||
{{range $idx, $val := .PublicKeys}}
|
||||
<div class="row form_field_pk_outer_row">
|
||||
<div class="form-group col-md-11">
|
||||
<textarea class="form-control" id="idPublicKey{{$idx}}" name="public_keys" rows="4"
|
||||
placeholder="Paste your public key here">{{$val}}</textarea>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="row form_field_pk_outer_row">
|
||||
<div class="form-group col-md-11">
|
||||
<textarea class="form-control" id="idPublicKey0" name="public_keys" rows="4"
|
||||
placeholder="Paste your public key here"></textarea>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field" disabled>
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mx-1">
|
||||
<button type="button" class="btn btn-secondary add_new_pk_field_btn">
|
||||
<i class="fas fa-plus"></i> Add new public key
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .CanSubmit}}
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
|
||||
{{end}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "extra_js"}}
|
||||
{{if .LoggedUser.CanManagePublicKeys}}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$("body").on("click", ".add_new_pk_field_btn", function () {
|
||||
var index = $(".form_field_pk_outer").find(".form_field_pk_outer_row").length;
|
||||
while (document.getElementById("idPublicKey"+index) != null){
|
||||
index++;
|
||||
}
|
||||
$(".form_field_pk_outer").append(`
|
||||
<div class="row form_field_pk_outer_row">
|
||||
<div class="form-group col-md-11">
|
||||
<textarea class="form-control" id="idPublicKey${index}" name="public_keys" rows="4"
|
||||
placeholder="Paste your public key here"></textarea>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
||||
$("body").on("click", ".remove_pk_btn_frm_field", function () {
|
||||
$(this).closest(".form_field_pk_outer_row").remove();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
Loading…
Reference in a new issue