Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to patch password/expires_at for permissions #4506

Merged
merged 4 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion docs/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Some known types:
- `io.cozy.jobs` and `io.cozy.triggers`, for [jobs](jobs.md)
- `io.cozy.oauth.clients`, to list and revoke [OAuth 2 clients](auth.md)

It is also possible to use a wildcard to use a doctype and its sub-doctypes if
It is also possible to use a wildcard to use a doctype and its sub-doctypes if
the doctype contains at least 3 `.`.
For example, `io.cozy.bank.*` will give access to `io.cozy.bank`,
`io.cozy.bank.accounts`, `io.cozy.bank.accounts.stats`,
Expand Down Expand Up @@ -415,6 +415,9 @@ give the contacts application the permissions to use it.

This route also accepts a [document metadata](https://github.com/cozy/cozy-doctypes/#document-metadata) to update document informations.

Giving an empty string for `password` or `expires_at` will remove it (while
omitting the field will keep the old value).

#### Request to add / remove codes with a document metadata

```http
Expand Down Expand Up @@ -444,6 +447,62 @@ Accept: application/vnd.api+json
}
```

#### Request to update the password and the expiration date of the sharing link

```http
PATCH /permissions/a340d5e0-d647-11e6-b66c-5fc9ce1e17c6 HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
```

```json
{
"data": {
"id": "a340d5e0-d647-11e6-b66c-5fc9ce1e17c6",
"type": "io.cozy.permissions",
"attributes": {
"password": "NewPassword",
"expires_at": "2025-01-01T00:00:00Z"
},
"cozyMetadata": {
"doctypeVersion": 1,
"metadataVersion": 1,
"updatedAt": "2019-05-14T12:00:37.372193145+02:00"
}
}
}
```

#### Request to remove the password and the expiration date of the sharing link

```http
PATCH /permissions/a340d5e0-d647-11e6-b66c-5fc9ce1e17c6 HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
```

```json
{
"data": {
"id": "a340d5e0-d647-11e6-b66c-5fc9ce1e17c6",
"type": "io.cozy.permissions",
"attributes": {
"password": "",
"expires_at": ""
},
"cozyMetadata": {
"doctypeVersion": 1,
"metadataVersion": 1,
"updatedAt": "2019-05-14T12:00:37.372193145+02:00"
}
}
}
```

#### Request to add permissions

```http
Expand Down
18 changes: 15 additions & 3 deletions model/permission/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type Permission struct {
Type string `json:"type,omitempty"`
SourceID string `json:"source_id,omitempty"`
Permissions Set `json:"permissions,omitempty"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
ExpiresAt interface{} `json:"expires_at,omitempty"`
nono marked this conversation as resolved.
Show resolved Hide resolved
Codes map[string]string `json:"codes,omitempty"`
ShortCodes map[string]string `json:"shortcodes,omitempty"`
Password interface{} `json:"password,omitempty"`
Expand Down Expand Up @@ -113,7 +113,12 @@ func (p *Permission) Expired() bool {
if p.ExpiresAt == nil {
return false
}
return p.ExpiresAt.Before(time.Now())
if expiresAt, _ := p.ExpiresAt.(string); expiresAt != "" {
if at, err := time.Parse(time.RFC3339, expiresAt); err == nil {
return at.Before(time.Now())
}
}
return true
}

// AddRules add some rules to the permission doc
Expand Down Expand Up @@ -495,7 +500,14 @@ func checkSetPermissions(set Set, parent *Permission) error {
}

// CreateShareSet creates a Permission doc for sharing by link
func CreateShareSet(db prefixer.Prefixer, parent *Permission, sourceID string, codes, shortcodes map[string]string, subdoc Permission, expiresAt *time.Time) (*Permission, error) {
func CreateShareSet(
db prefixer.Prefixer,
parent *Permission,
sourceID string,
codes, shortcodes map[string]string,
subdoc Permission,
expiresAt interface{},
) (*Permission, error) {
set := subdoc.Permissions
if err := checkSetPermissions(set, parent); err != nil {
return nil, err
Expand Down
59 changes: 51 additions & 8 deletions web/permissions/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func createPermission(c echo.Context) error {
return err
}

var expiresAt *time.Time
var expiresAt interface{}
if ttl != "" {
if d, errd := bigduration.ParseDuration(ttl); errd == nil {
ex := time.Now().Add(d)
Expand All @@ -145,6 +145,15 @@ func createPermission(c echo.Context) error {
tiny = false
}
}
} else {
tiny = false
if at, ok := subdoc.ExpiresAt.(string); ok {
expires, err := time.Parse(time.RFC3339, at)
if err != nil {
return jsonapi.InvalidAttribute("expires_at", err)
}
expiresAt = &expires
}
}

var codes map[string]string
Expand Down Expand Up @@ -357,19 +366,48 @@ func patchPermission(getPerms getPermsFunc, paramName string) echo.HandlerFunc {
patchSet := patch.Permissions != nil && len(patch.Permissions) > 0
patchCodes := len(patch.Codes) > 0

if patchCodes == patchSet {
return ErrPatchCodeOrSet
}

toPatch, err := getPerms(instance, c.Param(paramName))
if err != nil {
return err
}

if patchCodes {
if !current.CanUpdateShareByLink(toPatch) {
return permission.ErrNotParent
if !patchSet && !current.CanUpdateShareByLink(toPatch) {
return permission.ErrNotParent
}

if patchCodes == patchSet {
if patchSet {
return ErrPatchCodeOrSet
}
if patch.Password == nil && patch.ExpiresAt == nil {
return ErrPatchCodeOrSet
}
}

if pass, ok := patch.Password.(string); ok {
if pass == "" {
toPatch.Password = nil
} else {
hash, err := crypto.GenerateFromPassphrase([]byte(pass))
if err != nil {
return err
}
toPatch.Password = hash
}
}
if at, ok := patch.ExpiresAt.(string); ok {
if patch.ExpiresAt == "" {
toPatch.ExpiresAt = nil
} else {
expiresAt, err := time.Parse(time.RFC3339, at)
if err != nil {
return jsonapi.InvalidAttribute("expires_at", err)
}
toPatch.ExpiresAt = expiresAt
}
}

if patchCodes {
toPatch.PatchCodes(patch.Codes)
}

Expand Down Expand Up @@ -406,6 +444,11 @@ func patchPermission(getPerms getPermsFunc, paramName string) echo.HandlerFunc {
return err
}

// Don't send the password hash to the client
if toPatch.Password != nil {
toPatch.Password = true
}

return jsonapi.Data(c, http.StatusOK, &APIPermission{toPatch, nil}, nil)
}
}
Expand Down
Loading