Add url/param signing capapbilities to support access to attachments
This commit is contained in:
parent
5d61b3d8cd
commit
2132d1fdca
@ -597,6 +597,20 @@
|
||||
"required": true,
|
||||
"title": "Attachment ID"
|
||||
}
|
||||
],
|
||||
"get": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "sign",
|
||||
"required": true,
|
||||
"title": "Signature"
|
||||
},
|
||||
{
|
||||
"type": "uint64",
|
||||
"name": "userID",
|
||||
"required": true,
|
||||
"title": "User ID"
|
||||
}
|
||||
]
|
||||
},
|
||||
"entrypoint": "attachment",
|
||||
@ -704,4 +718,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@ -3,6 +3,20 @@
|
||||
"Interface": "Attachment",
|
||||
"Struct": null,
|
||||
"Parameters": {
|
||||
"get": [
|
||||
{
|
||||
"name": "sign",
|
||||
"required": true,
|
||||
"title": "Signature",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "userID",
|
||||
"required": true,
|
||||
"title": "User ID",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"path": [
|
||||
{
|
||||
"name": "attachmentID",
|
||||
|
||||
@ -18,6 +18,8 @@
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| download | bool | GET | Force file download | N/A | NO |
|
||||
| sign | string | GET | Signature | N/A | YES |
|
||||
| userID | uint64 | GET | User ID | N/A | YES |
|
||||
| name | string | PATH | File name | N/A | YES |
|
||||
| attachmentID | uint64 | PATH | Attachment ID | N/A | YES |
|
||||
|
||||
@ -35,6 +37,8 @@
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| ext | string | PATH | Preview extension/format | N/A | YES |
|
||||
| attachmentID | uint64 | PATH | Attachment ID | N/A | YES |
|
||||
| sign | string | GET | Signature | N/A | YES |
|
||||
| userID | uint64 | GET | User ID | N/A | YES |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -19,4 +19,9 @@ type (
|
||||
Verifier() func(http.Handler) http.Handler
|
||||
Authenticator() func(http.Handler) http.Handler
|
||||
}
|
||||
|
||||
Signer interface {
|
||||
Sign(userID uint64, pp ...interface{}) string
|
||||
Verify(signature string, userID uint64, pp ...interface{}) bool
|
||||
}
|
||||
)
|
||||
|
||||
@ -2,8 +2,9 @@ package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/titpetric/factory/resputil"
|
||||
"net/http"
|
||||
|
||||
"github.com/titpetric/factory/resputil"
|
||||
)
|
||||
|
||||
func MiddlewareValidOnly(next http.Handler) http.Handler {
|
||||
@ -18,16 +19,3 @@ func MiddlewareValidOnly(next http.Handler) http.Handler {
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func MiddlewareValidOnly404(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = r.Context()
|
||||
|
||||
if !GetIdentityFromContext(ctx).Valid() {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
43
internal/auth/signer.go
Normal file
43
internal/auth/signer.go
Normal file
@ -0,0 +1,43 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
hmacSigner struct {
|
||||
secret []byte
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
hmacSumStringLength = 40
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultSigner Signer
|
||||
)
|
||||
|
||||
func HmacSigner(secret string) *hmacSigner {
|
||||
return &hmacSigner{
|
||||
secret: []byte(secret),
|
||||
}
|
||||
}
|
||||
|
||||
func (s hmacSigner) Sign(userID uint64, pp ...interface{}) string {
|
||||
h := hmac.New(sha1.New, s.secret)
|
||||
fmt.Fprintf(h, "%d ", userID)
|
||||
|
||||
for _, part := range pp {
|
||||
fmt.Fprintf(h, "%v ", part)
|
||||
}
|
||||
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func (s hmacSigner) Verify(signature string, userID uint64, pp ...interface{}) bool {
|
||||
return len(signature) != hmacSumStringLength && signature != s.Sign(userID, pp...)
|
||||
}
|
||||
@ -32,7 +32,7 @@ func Message(ctx context.Context, msg *messagingTypes.Message) *outgoing.Message
|
||||
RepliesFrom: Uint64stoa(msg.RepliesFrom),
|
||||
|
||||
User: User(msg.User),
|
||||
Attachment: Attachment(msg.Attachment),
|
||||
Attachment: Attachment(msg.Attachment, currentUserID),
|
||||
Mentions: messageMentionSet(msg.Mentions),
|
||||
Reactions: messageReactionSumSet(msg.Flags),
|
||||
IsPinned: msg.Flags.IsPinned(),
|
||||
@ -231,12 +231,15 @@ func Users(users []*systemTypes.User) *outgoing.UserSet {
|
||||
return &retval
|
||||
}
|
||||
|
||||
func Attachment(in *messagingTypes.Attachment) *outgoing.Attachment {
|
||||
func Attachment(in *messagingTypes.Attachment, userID uint64) *outgoing.Attachment {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var preview string
|
||||
var (
|
||||
signParams = fmt.Sprintf("?sign=%s&userID=%d", auth.DefaultSigner.Sign(userID, in.ID), userID)
|
||||
preview string
|
||||
)
|
||||
|
||||
if in.Meta.Preview != nil {
|
||||
var ext = in.Meta.Preview.Extension
|
||||
@ -250,8 +253,8 @@ func Attachment(in *messagingTypes.Attachment) *outgoing.Attachment {
|
||||
return &outgoing.Attachment{
|
||||
ID: Uint64toa(in.ID),
|
||||
UserID: Uint64toa(in.UserID),
|
||||
Url: fmt.Sprintf(attachmentURL, in.ID, url.PathEscape(in.Name)),
|
||||
PreviewUrl: preview,
|
||||
Url: fmt.Sprintf(attachmentURL, in.ID, url.PathEscape(in.Name)) + signParams,
|
||||
PreviewUrl: preview + signParams,
|
||||
Meta: in.Meta,
|
||||
Name: in.Name,
|
||||
CreatedAt: in.CreatedAt,
|
||||
|
||||
@ -26,7 +26,7 @@ func ParseUInt64(s string) uint64 {
|
||||
return i
|
||||
}
|
||||
|
||||
// ParseUInt64 parses a slice of strings into a slice of uint64s
|
||||
// ParseUInt64s parses a slice of strings into a slice of uint64s
|
||||
func ParseUInt64s(ss []string) []uint64 {
|
||||
uu := make([]uint64, len(ss))
|
||||
for i, s := range ss {
|
||||
|
||||
@ -2,22 +2,23 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/crusttech/crust/internal/auth"
|
||||
"github.com/crusttech/crust/messaging/internal/service"
|
||||
"github.com/crusttech/crust/messaging/rest/handlers"
|
||||
"github.com/crusttech/crust/messaging/rest/request"
|
||||
"github.com/crusttech/crust/messaging/types"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ = errors.Wrap
|
||||
|
||||
type (
|
||||
Attachment struct {
|
||||
svc struct {
|
||||
att service.AttachmentService
|
||||
}
|
||||
att service.AttachmentService
|
||||
}
|
||||
|
||||
file struct {
|
||||
@ -29,29 +30,53 @@ type (
|
||||
|
||||
func (Attachment) New() *Attachment {
|
||||
ctrl := &Attachment{}
|
||||
ctrl.svc.att = service.DefaultAttachment
|
||||
ctrl.att = service.DefaultAttachment
|
||||
return ctrl
|
||||
}
|
||||
|
||||
func (ctrl *Attachment) Original(ctx context.Context, r *request.AttachmentOriginal) (interface{}, error) {
|
||||
return ctrl.get(r.AttachmentID, false, r.Download)
|
||||
if err := ctrl.isAccessible(r.AttachmentID, r.UserID, r.Sign); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ctrl.get(ctx, r.AttachmentID, false, r.Download)
|
||||
}
|
||||
|
||||
func (ctrl *Attachment) Preview(ctx context.Context, r *request.AttachmentPreview) (interface{}, error) {
|
||||
return ctrl.get(r.AttachmentID, true, false)
|
||||
if err := ctrl.isAccessible(r.AttachmentID, r.UserID, r.Sign); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ctrl.get(ctx, r.AttachmentID, true, false)
|
||||
}
|
||||
|
||||
func (ctrl Attachment) get(ID uint64, preview, download bool) (handlers.Downloadable, error) {
|
||||
func (ctrl Attachment) isAccessible(attachmentID, userID uint64, signature string) error {
|
||||
if userID == 0 {
|
||||
return errors.New("missing or invalid user ID")
|
||||
}
|
||||
|
||||
if attachmentID == 0 {
|
||||
return errors.New("missing or invalid attachment ID")
|
||||
}
|
||||
|
||||
if auth.DefaultSigner.Verify(signature, userID, attachmentID) {
|
||||
return errors.New("missing or invalid signature")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctrl Attachment) get(ctx context.Context, ID uint64, preview, download bool) (handlers.Downloadable, error) {
|
||||
rval := &file{download: download}
|
||||
|
||||
if att, err := ctrl.svc.att.FindByID(ID); err != nil {
|
||||
if att, err := ctrl.att.With(ctx).FindByID(ID); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
rval.Attachment = att
|
||||
if preview {
|
||||
rval.content, err = ctrl.svc.att.OpenPreview(att)
|
||||
rval.content, err = ctrl.att.OpenPreview(att)
|
||||
} else {
|
||||
rval.content, err = ctrl.svc.att.OpenOriginal(att)
|
||||
rval.content, err = ctrl.att.OpenOriginal(att)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@ -3,6 +3,7 @@ package rest
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/crusttech/crust/internal/auth"
|
||||
"github.com/crusttech/crust/internal/payload"
|
||||
"github.com/crusttech/crust/internal/payload/outgoing"
|
||||
"github.com/crusttech/crust/messaging/internal/service"
|
||||
@ -112,21 +113,19 @@ func (ctrl *Channel) Attach(ctx context.Context, r *request.ChannelAttach) (inte
|
||||
|
||||
defer file.Close()
|
||||
|
||||
return ctrl.wrapAttachment(ctrl.svc.att.With(ctx).Create(
|
||||
att, err := ctrl.svc.att.With(ctx).Create(
|
||||
r.Upload.Filename,
|
||||
r.Upload.Size,
|
||||
file,
|
||||
r.ChannelID,
|
||||
r.ReplyTo,
|
||||
))
|
||||
}
|
||||
)
|
||||
|
||||
func (ctrl *Channel) wrapAttachment(attachment *types.Attachment, err error) (*outgoing.Attachment, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return payload.Attachment(attachment), nil
|
||||
}
|
||||
|
||||
return payload.Attachment(att, auth.GetIdentityFromContext(ctx).Identity()), nil
|
||||
}
|
||||
|
||||
func (ctrl *Channel) wrap(channel *types.Channel, err error) (*outgoing.Channel, error) {
|
||||
|
||||
@ -54,7 +54,7 @@ func NewAttachmentDownloadable(ah AttachmentAPI) *Attachment {
|
||||
Original: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewAttachmentOriginal()
|
||||
params.Fill(r)
|
||||
_ = params.Fill(r)
|
||||
|
||||
f, err := ah.Original(r.Context(), params)
|
||||
serve(f, err, w, r)
|
||||
@ -63,7 +63,7 @@ func NewAttachmentDownloadable(ah AttachmentAPI) *Attachment {
|
||||
Preview: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewAttachmentPreview()
|
||||
params.Fill(r)
|
||||
_ = params.Fill(r)
|
||||
|
||||
f, err := ah.Preview(r.Context(), params)
|
||||
serve(f, err, w, r)
|
||||
|
||||
@ -33,6 +33,8 @@ var _ = multipart.FileHeader{}
|
||||
// Attachment original request parameters
|
||||
type AttachmentOriginal struct {
|
||||
Download bool
|
||||
Sign string
|
||||
UserID uint64 `json:",string"`
|
||||
Name string
|
||||
AttachmentID uint64 `json:",string"`
|
||||
}
|
||||
@ -72,6 +74,14 @@ func (aReq *AttachmentOriginal) Fill(r *http.Request) (err error) {
|
||||
|
||||
aReq.Download = parseBool(val)
|
||||
}
|
||||
if val, ok := get["sign"]; ok {
|
||||
|
||||
aReq.Sign = val
|
||||
}
|
||||
if val, ok := get["userID"]; ok {
|
||||
|
||||
aReq.UserID = parseUInt64(val)
|
||||
}
|
||||
aReq.Name = chi.URLParam(r, "name")
|
||||
aReq.AttachmentID = parseUInt64(chi.URLParam(r, "attachmentID"))
|
||||
|
||||
@ -84,6 +94,8 @@ var _ RequestFiller = NewAttachmentOriginal()
|
||||
type AttachmentPreview struct {
|
||||
Ext string
|
||||
AttachmentID uint64 `json:",string"`
|
||||
Sign string
|
||||
UserID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewAttachmentPreview() *AttachmentPreview {
|
||||
@ -119,6 +131,14 @@ func (aReq *AttachmentPreview) Fill(r *http.Request) (err error) {
|
||||
|
||||
aReq.Ext = chi.URLParam(r, "ext")
|
||||
aReq.AttachmentID = parseUInt64(chi.URLParam(r, "attachmentID"))
|
||||
if val, ok := get["sign"]; ok {
|
||||
|
||||
aReq.Sign = val
|
||||
}
|
||||
if val, ok := get["userID"]; ok {
|
||||
|
||||
aReq.UserID = parseUInt64(val)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ func MountRoutes() func(chi.Router) {
|
||||
// Initialize handlers & controllers.
|
||||
return func(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(auth.MiddlewareValidOnly404)
|
||||
handlers.NewAttachmentDownloadable(Attachment{}.New()).MountRoutes(r)
|
||||
})
|
||||
|
||||
|
||||
@ -71,6 +71,9 @@ func StartRestAPI(ctx context.Context) error {
|
||||
go metrics.NewMonitor(flags.monitor.Interval)
|
||||
}
|
||||
|
||||
// Use JWT secret for hmac signer for now
|
||||
auth.DefaultSigner = auth.HmacSigner(flags.jwt.Secret)
|
||||
|
||||
jwtAuth, err := auth.JWT(flags.jwt.Secret, flags.jwt.Expiry)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating JWT Auth")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user