Skip to content

Commit 89e77ec

Browse files
v0.13.0-beta Subscription Updates (#26)
* Updated subscription, authorization, how it works, directives, pdf diagrams
1 parent e73627b commit 89e77ec

23 files changed

+896
-468
lines changed

docs/advanced/directives.md

Lines changed: 79 additions & 73 deletions
Large diffs are not rendered by default.

docs/advanced/subscriptions.md

Lines changed: 137 additions & 24 deletions
Large diffs are not rendered by default.

docs/advanced/type-expressions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ These assumptions are made:
1212

1313
- Fields that return reference types **can be** null
1414
- Fields that return value types **cannot be** null
15-
- Fields that return Nullable value types (e.g. `int?`) **can be** be null
15+
- Fields that return Nullable value types (e.g. `int?`) **can be** be null.
1616
- When a field returns an object that implements `IEnumerable<TType>` it will be presented to GraphQL as a "list of `TType`".
1717

1818
Basically, if your method is able to return a value...then its valid as far as GraphQL is concerned.
@@ -49,7 +49,7 @@ query {
4949
</div>
5050
<br/>
5151

52-
This action method could return a `Donut` or return `null`. But should the `donut` field allow a null value? The code certainly does and the rules above say fields that return a reference type can be null...but that's not what's important. Its ultimately your decision to decide if a null donut is allowed, not the C# compiler and not the assumptions made by the library.
52+
This action method could return a `Donut` or returns `null`. But should the donut field, from a GraphQL perspective, allow a null return value? The code certainly does and the rules above say fields that return a reference type can be null...but that's not what's important. Its ultimately your decision to decide if a "null donut" is allowed, not the C# compiler and not the assumptions made by the library.
5353

5454
On one hand, if a null value is returned, regardless of it being valid, the _outcome_ of the field is the same. When we return a null no child fields are processed. On the other hand, if null is not allowed we need to tell someone, let them know its nulled out not because it simply _is_ null but because a schema violation occurred.
5555

@@ -59,7 +59,7 @@ Most of the time, using the `TypeExpression` property of a field declaration att
5959

6060
```csharp
6161

62-
// Declare that an MUST be returned (null is invalid)
62+
// Declare that a donut MUST be returned (null is invalid)
6363
// ----
6464
// Schema Syntax: Donut!
6565
[Query("donut", TypeExpression = TypeExpressions.IsNotNull)]
508 KB
Binary file not shown.
254 KB
Binary file not shown.
72.3 KB
Binary file not shown.

docs/controllers/actions.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -562,12 +562,12 @@ public class BakeryController : GraphController
562562

563563
```javascript
564564
query {
565-
searchDonuts(searchParams:
566-
name: "jelly*"
567-
filled: true
568-
dayOld: false){
569-
id
570-
name
565+
searchDonuts(
566+
name: "jelly*"
567+
filled: true
568+
dayOld: false){
569+
id
570+
name
571571
}
572572
}
573573
```
@@ -609,8 +609,7 @@ public class DonutSearchParams
609609
public class BakeryController : GraphController
610610
{
611611
[QueryRoot]
612-
public IEnumerable<Donut>
613-
SearchDonuts(DonutSearchParams searchParams)
612+
public IEnumerable<Donut> SearchDonuts(DonutSearchParams searchParams)
614613
{/* ... */}
615614
}
616615

docs/controllers/authorization.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public class BakeryController : GraphController
8080

8181
## Use of IAuthorizationService
8282

83-
Under the hood, GraphQL taps into your `IServiceProvider` to obtain a reference to the `IAuthorizationService` that gets created when you configure `.AddAuthorization()` for policy enforcement rules. Take a look at the [Field Authorization](https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet/Middleware/FieldSecurity/Components/FieldAuthorizationMiddleware.cs) Middleware Component for the full picture.
83+
Under the hood, GraphQL taps into your `IServiceProvider` to obtain a reference to the `IAuthorizationService` that gets created when you configure `.AddAuthorization()` for policy enforcement rules. Take a look at the [Schema Item Authorization Pipeline](https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Middleware/SchemaItemSecurity) for the full picture.
8484

8585
## When does Authorization Occur?
8686

@@ -90,21 +90,32 @@ _The Default "per field" Authorization workflow_
9090

9191
---
9292

93-
In the diagram above we can see that user authorization in GraphQL ASP.NET makes use of the result from [ASP.NET's security pipeline](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction) but makes no attempt to interact with it. Whether you use Kerberos tokens, OAUTH2, username/password, API tokens or if you support 2-factor authentication or one-time-use passwords, GraphQL doesn't care. The entirety of your authentication and authorization pipeline is executed by GraphQL, no special arrangements or configuration is needed.
93+
In the diagram above we can see that user authorization in GraphQL ASP.NET makes use of the result from [ASP.NET's security pipeline](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction). Whether you use Kerberos tokens, oauth2, username/password, API tokens or if you support 2-factor authentication or one-time-use passwords, GraphQL doesn't care. The entirety of your authentication and authorization scheme is executed by GraphQL, no special arrangements or configuration is needed.
9494

9595
> GraphQL ASP.NET draws from your configured authentication/authorization solution.
9696
97-
Field requests are passed through a [pipeline](../reference/how-it-works#middleware-pipelines) where field authorization is enforced as a middleware component before the data is queried (i.e. before the resolver is invoked). Should a requestor not be authorized for a given field a `null` value is resolved and a message added to the response.
97+
Execution directives and field resolutions are passed through a [pipeline](../reference/how-it-works#middleware-pipelines) where authorization is enforced as a series of middleware components before the respective handlers are invoked. Should a requestor not be authorized for a given schema item an action is taken:
9898

99-
Null propagation rules still apply to unauthorized fields meaning if the field cannot accept a null value its propagated up the field chain potentially nulling out a parent or parent of a parent depending on your schema.
10099

101-
By default, a single unauthorized result does not necessarily kill an entire query, it depends on the structure of your object graph. When a field request is terminated any down-stream child fields are discarded immediately but sibling fields or unrelated ancestors continue to execute as normal.
100+
## Field Authorizations
101+
102+
If a requestor is not authorized to a requested field a value of `null` is used as the resolved value and an error message is recorded to the query results.
103+
104+
Null propagation rules still apply to unauthorized fields meaning if the field cannot accept a null value, its propagated up the field chain potentially nulling out a parent or "parent of a parent" depending on your schema.
105+
106+
By default, a single unauthorized field result does not necessarily kill an entire query, it depends on the structure of your object graph and the query being executed. When a field request is terminated any down-stream child fields are discarded immediately but sibling fields or unrelated ancestors continue to execute as normal.
102107

103108
Since this authorization occurs "per field" and not "per controller action" its possible to define the same security chain for POCO properties. This allows you to effectively deny access, by policy, to a single property of an instantiated object. Performing security checks for every field of data (especially in parent/child scenarios) has a performance cost though, especially for larger data sets. For most scenarios enforcing security at the controller level is sufficient.
104109

105-
## Authorization Failures are Obfuscated
110+
### Field Authorization Failures are Obfuscated
111+
112+
When GraphQL denies a requestor access to a field a message naming the field path is added to the response. This message is generic on purpose; `"Access denied to field '[query]/bakery/donuts'"`. To view more targeted reasons, such as specific policy failures, you'll need to expose exceptions on the request or turn on [logging](../logging/structured-logging). GraphQL automatically raises the `FieldSecurityChallengeCompleted` log event at a `Warning` level when a security check fails.
106113

107-
When GraphQL denies a requestor access to a field a message naming the field path is added to the response. This message is generic on purpose, `"Access denied to field '[query]/bakery/donuts'."`. To view more targeted reasons, such as policy failures, you'll need to expose exceptions on the request or turn on [logging](../logging/structured-logging). GraphQL automatically raises the `FieldSecurityChallengeCompleted` log event at a `Warning` level when a security check fails.
114+
## Execution Directives Authorizations
115+
116+
Execution directives are applied to the _query document_ before a query plan is created and it is the query plan that determines what field resolvers should be called. As a result, execution directives have the potential to alter the document structure and change how a query might be resolved. Because of this, not executing a query directive has the potential to change (or not change) the expected query to be different than what the requestor asked for.
117+
118+
Therefore, if an execution directive fails authorization the query is rejected and not executed. The called will receive an error message as part of the response indicating the unauthorized directive. Like field authorization failures, the message is obfuscated and contains only a generic message. You'll need to expose exception on the request or turn on logging to see additional details.
108119

109120
## Authorization Methods
110121

@@ -126,3 +137,5 @@ public void ConfigureServices(IServiceCollection services)
126137
});
127138
}
128139
```
140+
141+
>Regardless of the authorization method chosen, **execution directives** are ALWAYS evaluated with a "per request" method. If a single execution directive fails, the whole query is dicarded.

docs/controllers/type-extensions.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,25 +121,25 @@ query {
121121

122122
When you declare a type extension it will only be invoked in context of the type being extended.
123123

124-
When we return a field of data from a property, an instance of the object must to exist in order to retrieve the property value. The same is true for a `type extension` except that instead of calling a property getter on the instance we're handing the entire instance to our method and letting it figure out what it needs to do with the data to resolve the field.
124+
When we return a value from a property, an instance of an object must exist in order to supply that value. That is to say if you want the `Name` property of a bakery, you need a bakery instance to retrieve it from. The same is true for a `type extension` except that instead of calling a property getter on the instance, graphql hands the entire object to our method and lets us figure out what needs to happen to resolve the field.
125125

126-
GraphQL senses the type being extended and finds a method parameter to match. It captures that parameter, hides it from the object graph and supplies it with the result of the parent field, in this case the resolution of field `bakery(id: 5)`.
126+
GraphQL inspects the type being extended and finds a parameter on the method to match it. It captures that parameter, hides it from the object graph, and fills it with the result of the parent field, in this case the resolution of field `bakery(id: 5)`.
127127

128128
This is immensely scalable:
129129

130130
- There are no wasted cycles fetching `CakeOrders` unless the requestor specifically asks for them.
131131
- We have full access to [type expression validation](../advanced/type-expressions) and [model validation](./model-state) for our other method parameters.
132132
- Since its a controller action we have full access to graph action results and can return `this.Ok()`, `this.Error()` etc. to give a rich developer experience.
133-
- [Field Authorization](./authorization) is also wired up for us.
133+
- [Field Security](./authorization) is also wired up for us.
134134
- The bakery model is greatly simplified.
135135

136136
#### Can every field be a type extension?
137137

138138
Theoretically, yes. But take a moment and think about performance. For basic objects with few dozen properties which is faster:
139139

140140
- One database query to retrieve 24 columns of a single record then only use six in a graph result.
141-
- Six separate database queries, one for each 10 character string value requested.
141+
- Six separate database queries, one for each string value requested.
142142

143-
Type extensions shine in parent-child relationships when preloading data is a concern but be careful not to go isolating every graph field just to avoid retrieving data unless absolutely necessary. Retrieving a few extra bytes of string data is negligible compared to querying a database 20 times. Your REST APIs likely do it as well and they even transmit that data down the wire to the client and the client has to discard it.
143+
Type extensions shine in parent-child relationships when preloading data is a concern but be careful not to go isolating every graph field just to avoid retrieving data. Fetching a few extra bytes from a database is negligible compared to querying a database 20 individual times. Your REST APIs likely do it as well and they even transmit that data down the wire to the client and the client has to discard it.
144144

145145
It comes down to your use case. There are times when it makes sense to query data separately using type extensions and times when preloading whole objects is better. For many applications, once you've deployed to production, the queries being executed are finite. Design your model objects and extensions to be performant in the ways your data is being requested, not in the ways it _could be_ requested.

docs/development/debugging.md

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,3 @@ public void ConfigureServices(IServiceCollection services)
2121
});
2222
}
2323
```
24-
25-
## Increase the Query Timeout
26-
27-
GraphQL will automatically abandon long running queries to prevent a resource drain. It may be helpful to up this timeout length in development. By default the timeout is `1 minute`.
28-
29-
```csharp
30-
// Startup.cs
31-
public void ConfigureServices(IServiceCollection services)
32-
{
33-
// Extending the default query timeout can help
34-
// during extended debug sessions
35-
services.AddGraphQL(options =>
36-
{
37-
options.ExecutionOptions.QueryTimeout = TimeSpan.FromMinutes(30);
38-
});
39-
}
40-
```

0 commit comments

Comments
 (0)