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

Authorization #145

Merged
merged 7 commits into from
Jul 2, 2020
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ lerna-debug.log*

node_modules/
*.tsbuildinfo
.yarn-integrity
.yarn-integrity
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"debug.node.autoAttach": "on"
}
90 changes: 48 additions & 42 deletions docs/authorization_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
Create a new object policy that describes an access logic that can be attached to a graphql field.
Some examples:

### Policy that always allows access:
### Policy that always allows access

```yaml
kind: Policy
Expand All @@ -28,7 +28,7 @@ Spec:
code: {'result': 'allow'}
```

### Policy that allows access only if the issuer is "abc.com":
### Policy that allows access only if the issuer is "abc.com"

```yaml
kind: Policy
Expand All @@ -37,10 +37,13 @@ metadata:
name: abc-user
Spec:
type: js-expression
code: { "result": (data.jwt.issuer === "abc.com") ? "allow" : "deny" }
code: { "result": (input.args.issuer === "abc.com") ? "allow" : "deny" }
args:
issuer: String!
```

`jwt` is available on the data object to validate fields from the web token
The `args` are available to use on the input object.
`jwt` is available for parameter injection in args using `issuer: "{jwt.issuer}"`

### Policy that allows access only if the subject matches the provided userId argument:

Expand All @@ -51,16 +54,15 @@ metadata:
name: my-user
Spec:
type: js-expression
code: { "result": (data.jwt.sub === data.args.userId) ? "allow" : "deny"}
code: { "result": (input.args.sub === input.args.userId) ? "allow" : "deny"}
args:
userId: ID!
sub: ID!
```

The `args` are available to use on the data object

_Note the js-expression type is an example of a possible type and not planned to be implemented at this time._

### Policy that uses a graphql query to fetch additional data and allows based on the results and an argument:
### Policy that uses a graphql query to fetch additional data and allows based on the results and an argument

```yaml
kind: Policy
Expand All @@ -72,29 +74,29 @@ Spec:
code: |
allow = false
allow = {
data.args.userId == data.queries.familyQuery.family.members[_].id
input.args.userId == input.query.user.family.members[_].id
}
args:
userId: ID!
queries:
- type: graphql
paramName: familyQuery
graphql:
query: |
{
user({jwt.sub}) {
family {
members {
id
}
}
}
sub: ID!
query:
gql: |
query ($sub: String!) {
user(sub: $sub) {
family {
members {
id
}
}
}
}
variables:
sub: '{args.sub}'
```

Queries support the standard Stitch parameter injection syntax, but only support the `jwt` and `args` objects for injection

### Same as the previous policy, but evaluates the query while opa is running:
### Same as the previous policy, but evaluates the query while opa is running

```yaml
kind: Policy
Expand All @@ -104,18 +106,19 @@ metadata:
Spec:
type: opa
code: |
query = sprintf(“graphql { user(%s) {family { members { id} } } }”, data.jwt.sub)
query = sprintf(“graphql { user(%s) {family { members { id} } } }”, input.args.sub)
allow = false
allow = {
data.args.userId == data.query.family.members[_].id
input.args.userId == input.query.family.members[_].id
}
args:
userId: ID!
sub: ID!
```

This example will always evaluate the graphql query, but generally this approach should be used when conditional side effect evaluation is needed

### Policy that uses a policy query and allows based on the results:
### Policy that uses another policy as query and allows based on the results

```yaml
kind: Policy
Expand All @@ -127,29 +130,31 @@ Spec:
code: |
allow = false
allow = {
data.queries.myUserPolicy == true
input.query.myUserPolicy == true
}
args:
userId: ID!
queries:
- type: policy
paramName: myUserPolicy
policy:
policyName: my-user
args:
userId: '{args.userId}'
query:
gql: |
query(userId: ID!) {
policy.my_user(userId: $userId) {
allow
}
}
variables:
userId: '{args.userId}'
```

`args` for query policy can use parameter injection from the `jwt` and `args` objects, similarly to the graphql query

### Attach policy to fields:

```
```gql
type User {
ID: ID!
Picture: String @policy-some-ns-public
Friends: [User] @policy-some-ns-abc-user
Email: String @policy-some-ns-my-user(userId: "{source.UserId}")
Email: String @policy-some-ns-my-user(userId: "{source.UserId}", sub: "{jwt.sub}")
NickName: @policy-some-ns-my-user-family(userId: "{source.UserId}")
}
```
Expand All @@ -160,7 +165,7 @@ Adding args support to policies can ease memoization and remove hidden dependenc

Initially, we will support a single `@policy` directive that gets an array of policy names and their args:

```
```gql
type User {
Picture: String @policy(policies: [
{ namespace: "some-ns", name: “my-user”, args: { userId: “{source.UserId}” } },
Expand All @@ -176,13 +181,13 @@ Later on, for convenience and type safety, we will add an alternative syntax or
1. Client requests a graphql query that includes a field that has a `@policy` directive.
2. Server activates the policy resolver and reads the arguments with parameter injection.
3. The resolver passes the args to a policy executor.
The policy executor invokes the right binding (see policy implementation contract) and manages the queries for the policy.
The policy executor invokes the right binding (see policy implementation contract) and manages the query for the policy.
4. The policy executor should return true/false if the user has access or a string describing the failure.
5. If the user has access, return the field value. Otherwise, return an error.

## Policy implementation contract

The naive js contract can be implemented with a generator, that has a result of true/false and can yield queries.
The naive js contract can be implemented with a generator, that has a result of true/false and can yield query.
All queries and policy evaluation itself are memoized for a request context for same query/policy arguments.

```typescript
Expand Down Expand Up @@ -267,18 +272,19 @@ Spec:
code: |
allow = false
allow {
data.jwt.claims[data.args.claims[i]] == data.args.values[i]
input.args.jwtClaims[input.args.claims[i]] == input.args.values[i]
}
args:
claims: [String]
values: [String]
jwtClaims: JSONObject
```

Usage:

```
```gql
type User {
ID: ID!
Picture: String @policy-some-ns-has-claims(claims:["issuer", "sub"], values: ["soluto.com", "{source.UserId}"]
Picture: String @policy-some-ns-has-claims(claims:["issuer", "sub"], values: ["soluto.com", "{source.UserId}"], jwtClaims: "{jwt.claims}")
}
```
Loading