mirror of
https://github.com/coder/coder.git
synced 2025-07-06 15:41:45 +00:00
Just moved `rbac.Action` -> `policy.Action`. This is for the stacked PR to not have circular dependencies when doing autogen. Without this, the autogen can produce broken golang code, which prevents the autogen from compiling. So just avoiding circular dependencies. Doing this in it's own PR to reduce LoC diffs in the primary PR, since this has 0 functional changes.
128 lines
3.6 KiB
Go
128 lines
3.6 KiB
Go
package rbac
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
|
|
"github.com/open-policy-agent/opa/rego"
|
|
"github.com/open-policy-agent/opa/topdown"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/v2/coderd/httpapi/httpapiconstraints"
|
|
"github.com/coder/coder/v2/coderd/rbac/policy"
|
|
)
|
|
|
|
const (
|
|
// errUnauthorized is the error message that should be returned to
|
|
// clients when an action is forbidden. It is intentionally vague to prevent
|
|
// disclosing information that a client should not have access to.
|
|
errUnauthorized = "rbac: forbidden"
|
|
)
|
|
|
|
// UnauthorizedError is the error type for authorization errors
|
|
type UnauthorizedError struct {
|
|
// internal is the internal error that should never be shown to the client.
|
|
// It is only for debugging purposes.
|
|
internal error
|
|
|
|
// These fields are for debugging purposes.
|
|
subject Subject
|
|
action policy.Action
|
|
// Note only the object type is set for partial execution.
|
|
object Object
|
|
|
|
output rego.ResultSet
|
|
}
|
|
|
|
// Ensure we implement the IsUnauthorized interface.
|
|
var _ httpapiconstraints.IsUnauthorizedError = (*UnauthorizedError)(nil)
|
|
|
|
// IsUnauthorized implements the IsUnauthorized interface.
|
|
func (UnauthorizedError) IsUnauthorized() bool {
|
|
return true
|
|
}
|
|
|
|
// IsUnauthorizedError is a convenience function to check if err is UnauthorizedError.
|
|
// It is equivalent to errors.As(err, &UnauthorizedError{}).
|
|
func IsUnauthorizedError(err error) bool {
|
|
return errors.As(err, &UnauthorizedError{})
|
|
}
|
|
|
|
// ForbiddenWithInternal creates a new error that will return a simple
|
|
// "forbidden" to the client, logging internally the more detailed message
|
|
// provided.
|
|
func ForbiddenWithInternal(internal error, subject Subject, action policy.Action, object Object, output rego.ResultSet) *UnauthorizedError {
|
|
return &UnauthorizedError{
|
|
internal: internal,
|
|
subject: subject,
|
|
action: action,
|
|
object: object,
|
|
output: output,
|
|
}
|
|
}
|
|
|
|
func (e UnauthorizedError) Unwrap() error {
|
|
return e.internal
|
|
}
|
|
|
|
func (e *UnauthorizedError) longError() string {
|
|
return fmt.Sprintf(
|
|
"%s: (subject: %v), (action: %v), (object: %v), (output: %v)",
|
|
errUnauthorized, e.subject, e.action, e.object, e.output,
|
|
)
|
|
}
|
|
|
|
// Error implements the error interface.
|
|
func (e UnauthorizedError) Error() string {
|
|
if flag.Lookup("test.v") != nil {
|
|
return e.longError()
|
|
}
|
|
return errUnauthorized
|
|
}
|
|
|
|
// Internal allows the internal error message to be logged.
|
|
func (e *UnauthorizedError) Internal() error {
|
|
return e.internal
|
|
}
|
|
|
|
func (e *UnauthorizedError) SetInternal(err error) {
|
|
e.internal = err
|
|
}
|
|
|
|
func (e *UnauthorizedError) Input() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"subject": e.subject,
|
|
"action": e.action,
|
|
"object": e.object,
|
|
}
|
|
}
|
|
|
|
// Output contains the results of the Rego query for debugging.
|
|
func (e *UnauthorizedError) Output() rego.ResultSet {
|
|
return e.output
|
|
}
|
|
|
|
// As implements the errors.As interface.
|
|
func (*UnauthorizedError) As(target interface{}) bool {
|
|
if _, ok := target.(*UnauthorizedError); ok {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// correctCancelError will return the correct error for a canceled context. This
|
|
// is because rego changes a canceled context to a topdown.CancelErr. This error
|
|
// is not helpful if the code is "canceled". To make the error conform with the
|
|
// rest of our canceled errors, we will convert the error to a context.Canceled
|
|
// error. No good information is lost, as the topdown.CancelErr provides the
|
|
// location of the query that was canceled, which does not matter.
|
|
func correctCancelError(err error) error {
|
|
e := new(topdown.Error)
|
|
if xerrors.As(err, &e) || e.Code == topdown.CancelErr {
|
|
return context.Canceled
|
|
}
|
|
return err
|
|
}
|