Files
warmbly/internal/api/handler/access.go
T

106 lines
3.5 KiB
Go

package handler
import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/warmbly/warmbly/internal/api/middleware"
"github.com/warmbly/warmbly/internal/app/aitools"
"github.com/warmbly/warmbly/internal/errx"
"github.com/warmbly/warmbly/internal/models"
)
// hasAccess is middleware.RequireAccess as an inline check, for a permission a
// handler needs only when an optional request field is present.
func (h *Handler) hasAccess(c *gin.Context, orgPerm models.OrganizationPermission, apiPerm uint64) *errx.Error {
switch middleware.GetAuthType(c) {
case middleware.AuthTypeAPIKey, middleware.AuthTypeOAuth:
if !models.HasAPIPermission(middleware.GetAPIKeyPermissions(c), apiPerm) {
return errx.New(errx.Forbidden, "insufficient API key permissions")
}
if middleware.GetAuthType(c) == middleware.AuthTypeOAuth {
if perms, ok := middleware.GetMemberPermissions(c); !ok || !perms.HasPermission(orgPerm) {
return errx.New(errx.Forbidden, "the member who authorized this app does not have this permission")
}
}
return nil
default:
if h.OrganizationService == nil {
return nil
}
userID, err := middleware.GetUserUUID(c)
if err != nil {
return errx.ErrUnauthorized
}
orgID := middleware.GetOrganizationID(c)
if orgID == nil {
return errx.ErrNoOrganization
}
has, xerr := h.OrganizationService.HasPermission(c.Request.Context(), *orgID, userID, orgPerm)
if xerr != nil {
return xerr
}
if !has {
return errx.ErrForbidden
}
return nil
}
}
// bindOAuthMember holds an OAuth caller's tools to its member's permissions as well as its scopes.
func bindOAuthMember(c *gin.Context, inv *aitools.Invocation) {
if middleware.GetAuthType(c) != middleware.AuthTypeOAuth {
return
}
inv.ActsForMember = true
inv.OrgPerms, _ = middleware.GetMemberPermissions(c)
}
// apiKeyCeiling is the widest permission set the caller may put on an API key.
func (h *Handler) apiKeyCeiling(c *gin.Context) (uint64, *errx.Error) {
if middleware.GetAuthType(c) == middleware.AuthTypeAPIKey {
return middleware.GetAPIKeyPermissions(c), nil
}
member, xerr := h.callerMember(c)
if xerr != nil {
return 0, xerr
}
return models.APIPermissionsFor(member), nil
}
// callerMember is the caller's membership in the request's workspace, or nil.
func (h *Handler) callerMember(c *gin.Context) (*models.OrganizationMember, *errx.Error) {
if m := middleware.GetAuthMember(c); m != nil {
return m, nil
}
orgID := middleware.GetOrganizationID(c)
if orgID == nil {
return nil, errx.ErrNoOrganization
}
userID, err := middleware.GetUserUUID(c)
if err != nil {
return nil, errx.ErrUnauthorized
}
if h.OrganizationService == nil {
return nil, errx.ErrForbidden
}
return h.OrganizationService.GetMembership(c.Request.Context(), *orgID, userID)
}
// mailboxAllowed enforces an API key's mailbox allow-list on an account id
// taken from a request body, the way RequireAPIKeyEmailAccountParam does for
// a route parameter.
func mailboxAllowed(c *gin.Context, accountID uuid.UUID) *errx.Error {
if !middleware.APIKeyAllowsEmailAccount(c, accountID) {
return errx.New(errx.Forbidden, "email account is not allowed for this API key")
}
return nil
}
// apiKeyMailboxLimited is the stable code for a mailbox-limited key refused on a workspace-wide surface.
const apiKeyMailboxLimited = "api_key_mailbox_limited"
// keyMailboxLimited reports whether the caller is a key held to an allowlist of mailboxes.
func keyMailboxLimited(c *gin.Context) bool {
return middleware.GetAuthType(c) != "jwt" && len(middleware.GetAPIKeyAllowedEmailAccounts(c)) > 0
}