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

PXD-2730: client permission #53

Merged
merged 4 commits into from
May 1, 2019
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
132 changes: 81 additions & 51 deletions arborist/auth.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package arborist

import (
"database/sql"
"encoding/json"
"fmt"
"strings"
Expand Down Expand Up @@ -87,6 +88,7 @@ func (requestJSON *AuthRequestJSON_Request) UnmarshalJSON(data []byte) error {

type AuthRequest struct {
Username string
ClientID string
Policies []string
Resource string
Service string
Expand All @@ -98,13 +100,14 @@ type AuthResponse struct {
Auth bool `json:"auth"`
}

// Authorize a request where the end user is anonymous, so there is no token
// involved, and access is granted only through the built-in anonymous group.
func authorizeAnonymous(request *AuthRequest) (*AuthResponse, error) {
func parse(resource string) (Expression, []string, []string, error) {
// parse the resource string
exp, args, err := Parse(request.Resource)
exp, args, err := Parse(resource)
if err != nil {
return nil, err
// TODO (rudyardrichter, 2019-04-05): this can return some pretty
// unintelligible errors from the yacc code. so far callers are OK to
// validate inputs, but could do better to return more readable errors
return nil, nil, nil, err
}

resources := make([]string, len(args))
Expand All @@ -113,6 +116,14 @@ func authorizeAnonymous(request *AuthRequest) (*AuthResponse, error) {
resources[i] = formatPathForDb(arg)
}

return exp, args, resources, nil
}

// Authorize a request where the end user is anonymous, so there is no token
// involved, and access is granted only through the built-in anonymous group.
func authorizeAnonymous(request *AuthRequest) (*AuthResponse, error) {
exp, args, resources, err := parse(request.Resource)

// run authorization query
rows, err := request.stmts.Query(
`
Expand Down Expand Up @@ -148,25 +159,7 @@ func authorizeAnonymous(request *AuthRequest) (*AuthResponse, error) {
return nil, err
}

// build the map for evaluation
i := 0
vars := make(map[string]bool)
for rows.Next() {
var result bool
err = rows.Scan(&result)
if err != nil {
return nil, err
}
vars[args[i]] = result
i++
}
if i != len(args) {
// user not found (i = 0)
return &AuthResponse{false}, nil
}

// evaluate the result
rv, err := Eval(exp, vars)
rv, err := evaluate(exp, args, rows)
if err != nil {
return nil, err
}
Expand All @@ -177,23 +170,8 @@ func authorizeAnonymous(request *AuthRequest) (*AuthResponse, error) {
// The given resource can be an expression of slash-separated resource paths
// connected with `and`, `or` or `not`. The priority of these boolean operators
// is: `not > and > or`. When in doubt, use parenthesises to specify explicitly.
func authorize(request *AuthRequest) (*AuthResponse, error) {
// parse the resource string
exp, args, err := Parse(request.Resource)
if err != nil {
// TODO (rudyardrichter, 2019-04-05): this can return some pretty
// unintelligible errors from the yacc code. so far callers are OK to
// validate inputs, but could do better to return more readable errors
return nil, err
}

resources := make([]string, len(args))
// format resource path for DB
for i, arg := range args {
resources[i] = formatPathForDb(arg)
}

// run authorization query
func authorizeUser(request *AuthRequest) (*AuthResponse, error) {
exp, args, resources, err := parse(request.Resource)
rows, err := request.stmts.Query(
`
SELECT coalesce(text2ltree("unnest") <@ allowed, FALSE) FROM (
Expand Down Expand Up @@ -240,29 +218,73 @@ func authorize(request *AuthRequest) (*AuthResponse, error) {
return nil, err
}

rv, err := evaluate(exp, args, rows)
if err != nil {
return nil, err
}
return &AuthResponse{rv}, nil
}

// This is similar as authorizeUser, only that this method checks for clientID only
func authorizeClient(request *AuthRequest) (*AuthResponse, error) {
exp, args, resources, err := parse(request.Resource)
rows, err := request.stmts.Query(
`
SELECT coalesce(text2ltree("unnest") <@ allowed, FALSE) FROM (
SELECT array_agg(resource.path) AS allowed FROM client
JOIN client_policy ON client_policy.client_id = client.id
JOIN policy_resource ON policy_resource.policy_id = client_policy.policy_id
JOIN resource ON resource.id = policy_resource.resource_id
WHERE client.external_client_id = $1
AND EXISTS (
SELECT 1 FROM policy_role
JOIN permission ON permission.role_id = policy_role.role_id
WHERE policy_role.policy_id = client_policy.policy_id
AND permission.service = $2
AND permission.method = $3
)
) _, unnest($4::text[]);
`,
request.ClientID, // $1
request.Service, // $2
request.Method, // $3
pq.Array(resources), // $4
)
if err != nil {
return nil, err
}

rv, err := evaluate(exp, args, rows)
if err != nil {
return nil, err
}
return &AuthResponse{rv}, nil
}

func evaluate(exp Expression, args []string, rows *sql.Rows) (bool, error) {
// build the map for evaluation
i := 0
vars := make(map[string]bool)
for rows.Next() {
var result bool
err = rows.Scan(&result)
err := rows.Scan(&result)
if err != nil {
return nil, err
return false, err
}
vars[args[i]] = result
i++
}
if i != len(args) {
// user not found (i = 0)
return &AuthResponse{false}, nil
return false, nil
}

// evaluate the result
rv, err := Eval(exp, vars)
if err != nil {
return nil, err
return false, err
}
return &AuthResponse{rv}, nil
return rv, nil
}

func authorizedResources(db *sqlx.DB, request *AuthRequest) ([]ResourceFromQuery, error) {
Expand Down Expand Up @@ -319,14 +341,22 @@ func authorizedResources(db *sqlx.DB, request *AuthRequest) ([]ResourceFromQuery
CAST ((ltree2text(resource.path) || '.*{1}') AS lquery)
)
) AS subresources
FROM usr
LEFT JOIN usr_policy ON usr.id = usr_policy.usr_id
LEFT JOIN policy_resource ON policy_resource.policy_id = usr_policy.policy_id
FROM (
SELECT usr_policy.policy_id
FROM usr
JOIN usr_policy ON usr.id = usr_policy.usr_id
WHERE usr.name = $1
UNION
SELECT client_policy.policy_id
FROM client
JOIN client_policy ON client_policy.client_id = client.id
WHERE client.external_client_id = $2
) policies
LEFT JOIN policy_resource ON policy_resource.policy_id = policies.policy_id
LEFT JOIN resource ON resource.id = policy_resource.resource_id
WHERE usr.name = $1
`
resources := []ResourceFromQuery{}
err := db.Select(&resources, stmt, request.Username)
err := db.Select(&resources, stmt, request.Username, request.ClientID)
if err != nil {
return nil, err
}
Expand Down
Loading