graphql-dotnet / authorization

A toolset for authorizing access to graph types for GraphQL .NET.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Correct way to hide schema?

OpenSpacesAndPlaces opened this issue · comments

@joemcbride

I only want GraphiQL/introspection look-ups to work if the application is running from localhost - otherwise I want to be able to add an "AuthorizeWith" specifically to that operation.

Otherwise I'd still want everything below that to follow the appropriate policies.
The trouble I'm running it to is the only way I can see to-do that is to stick a policy directly on the "query" graph type, but then it breaks the security free operations.

e.g. I want :

query .. my schema - blocked if not logged in (following policy)
query .. my security free table - allowed.

I have all the code needed/working - it's just this niche case I'm stuck on.

I'm hoping I just missed something and there is simple workaround for this.

Thanks!!!

For now I'm doing like:

if (result.Operation.Name == "IntrospectionQuery" && !IsLocalhost())
		{

		}

Hi. Thank you for your contribution. May I ask you to provide us with example code. Thanks! PS: I'm stuck on the same issue.

@bioharz

Wherever you are running:
ExecutionResult result = await _executer.ExecuteAsync

Just add an condition afterwards like:

if (result.Operation.Name == "IntrospectionQuery" && !YourMethodThatReturnsIfDocsShouldShow)
{
     //Workaround
     result.Errors.Add(new ExecutionError(nameof(HttpStatusCode.Forbidden)));
}

The client application calling the api controls the OperationName, so someone could easily bypass that check by giving their query a different name. The only way to truly disable being able to query introspection types would be to check for the __schema field. You could write a validation rule to do that.

Also, you would want to do this check before the query is executed vs. after. Validation rules run before the query is executed.

The client application calling the api controls the OperationName, so someone could easily bypass that check by giving their query a different name.

I'm a little confused by that - if his goal was the same mine, then the intention wasn't to block any other request type that isn't public, just the documentation look-ups. Otherwise in my model everything else is handled with MetaData and/or AuthorizationSettings.

To be clear with my question - what else could somebody send to the server to to-do an "IntrospectionQuery" besides "IntrospectionQuery"?

This is an introspection query with a different Operation name.

query aNameThatIChoose {
  __schema {
    types {
      name
    }
  }
}

@joemcbride

public class SchemaValidationRule : IValidationRule
{
    public INodeVisitor Validate(ValidationContext context)
    {
        return new EnterLeaveListener(_ =>
        {
            _.Match<Field>(f =>
            {
                if (!ICanBeHere && f.Name == "__schema")
                {
                    var error = new ValidationError(
                        context.OriginalQuery,
                        ((int)HttpStatusCode.Forbidden).ToString(),
                        nameof(HttpStatusCode.Forbidden));
                    context.ReportError(error);
                }

                return;
            });
        });
    }
}

From your suggestion I added the above - but it seems a little heavy handed since it's going to run over everything in the case it's not there.

Is there a more efficient approach?

Is this better than or is there a downside to a string check on:

if(myquery.IndexOf("__schema",StringComparison.InvariantCultureIgnoreCase) >= 0)
//Stop processing

ExecutionResult result = await _executer.ExecuteAsync(_ =>
{
...
_.Query = myquery;
...
}

Yes, there is a downside
query oops_not_a__schema { client { permissions__schema name } }

Fair enough, but is that a real world false positive someone would run into?
I can't recall any instance I've ever include double underscore in a property.

Or really my question, is doing a string check somehow less secure in this instance?
Is there someway it could be skirted?

is doing a string check somehow less secure in this instance?

Probably not. Doing the check as a validation rule though you get the error in the GraphQL format and mitigate any potential bugs. All of the fields in the request are already being iterated over with other validation rules. Adding one more rule should not be a huge overhead and does not iterate over the whole query again, it just calls your validation rule while it iterates over them.

Thanks for clarifying!!

What if I just want to leave out bits of schema that a particular user shouldn't see given their authorization status? Does returning forbidden play well with playground, voyager etc?

@AndyEdmonds

I don't know the right way to handle what you're saying, but the worst case would always be to pre-generate the schema - and create a separate controller for auth/loading separate copies.

Another possibility might be to modify something from "SchemaValidationRule : IValidationRule" above. Schema is one of the first matches you'd hit - so in-theory you could save that meta information to context.UserContext and then use that as a filter basis for other "IValidationRule" classes.

Both of these I would call 'hacks' to your problem.

Thanks @OpenSpacesAndPlaces,
In my case I've, perhaps foolishly, mixed functionality that is to do with maintenance of the site with functionality open to all and signed in users. I can happily stop users without the right authority from accessing things they shouldn't using .AuthorizeWith("UserPolicy"); but it can still be seen using schema discovery.
Perhaps I should move this functionality to a separate schema and serve two from the same source, if this is possible, perhaps serving something on say /graphql/admin
Alternatively adding annotation to the schema along the lines of deprecation might be good.

Schema Introspection Filtering is in the works: graphql-dotnet/graphql-dotnet#1179

Closed as answered.