Skip to content

Feat(GraphQL): This PR adds auth switch in GraphQL authorization header. #6779

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

Merged
merged 23 commits into from
Oct 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8376670
added code for authorization switch
JatinDev543 Oct 20, 2020
366a295
changed error message
JatinDev543 Oct 21, 2020
bfc8974
added unit tests
JatinDev543 Oct 21, 2020
408bffc
added e2e tests
JatinDev543 Oct 22, 2020
f152bad
clean code
JatinDev543 Oct 22, 2020
7d9b566
moved tests and clean code
JatinDev543 Oct 22, 2020
c78882a
cleaned tests
JatinDev543 Oct 23, 2020
95ba416
clean code
JatinDev543 Oct 23, 2020
2ab68fb
clean code a bit
JatinDev543 Oct 23, 2020
9766c23
Merge branch 'master' of github.com:dgraph-io/dgraph into jatin/GRAPH…
JatinDev543 Oct 23, 2020
df292af
removed extra part
JatinDev543 Oct 23, 2020
73a13ae
Merge branch 'master' of github.com:dgraph-io/dgraph into jatin/GRAPH…
JatinDev543 Oct 23, 2020
1e96a6b
added false switch by default
JatinDev543 Oct 23, 2020
7be00a8
added closedByDefault for RS256 test case
JatinDev543 Oct 26, 2020
2e53e21
fixed tests
JatinDev543 Oct 27, 2020
9f60182
modified tests and resolved comments.
JatinDev543 Oct 28, 2020
1931e46
Merge branch 'master' of github.com:dgraph-io/dgraph into jatin/GRAPH…
JatinDev543 Oct 28, 2020
fa0bf5c
merge masters,removed antidependency errors and resolved comments
JatinDev543 Oct 28, 2020
941314a
removed anti dependency error
JatinDev543 Oct 28, 2020
76b5331
Merge branch 'master' of github.com:dgraph-io/dgraph into jatin/GRAPH…
JatinDev543 Oct 29, 2020
b6ea68f
Merge branch 'master' of github.com:dgraph-io/dgraph into jatin/GRAPH…
JatinDev543 Oct 29, 2020
e367f6a
Merge branch 'master' of github.com:dgraph-io/dgraph into jatin/GRAPH…
JatinDev543 Oct 29, 2020
43e5f6e
fixed formatting
JatinDev543 Oct 29, 2020
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
12 changes: 8 additions & 4 deletions graphql/authorization/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type AuthMeta struct {
Algo string
SigningMethod jwt.SigningMethod `json:"-"` // Ignoring this field
Audience []string
ClosedByDefault bool
sync.RWMutex
}

Expand Down Expand Up @@ -113,7 +114,6 @@ func Parse(schema string) (*AuthMeta, error) {
return nil, nil
}
authInfo := schema[authInfoIdx:]

err := json.Unmarshal([]byte(authInfo[len(AuthMetaHeader):]), &meta)
if err == nil {
if err := meta.validate(); err != nil {
Expand Down Expand Up @@ -246,6 +246,7 @@ func SetAuthMeta(m *AuthMeta) {
authMeta.Algo = m.Algo
authMeta.SigningMethod = m.SigningMethod
authMeta.Audience = m.Audience
authMeta.ClosedByDefault = m.ClosedByDefault
}

// AttachAuthorizationJwt adds any incoming JWT authorization data into the grpc context metadata.
Expand Down Expand Up @@ -324,9 +325,12 @@ func ExtractCustomClaims(ctx context.Context) (*CustomClaims, error) {
// return CustomClaims containing jwt and authvariables.
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return &CustomClaims{}, nil
if authMeta.ClosedByDefault {
return &CustomClaims{}, fmt.Errorf("a valid JWT is required but was not provided")
} else {
return &CustomClaims{}, nil
}
}

jwtToken := md.Get(string(AuthJwtCtxKey))
if len(jwtToken) == 0 {
return &CustomClaims{}, nil
Expand Down Expand Up @@ -468,7 +472,7 @@ func (a *AuthMeta) FetchJWKs() error {
var maxAge int64

if resp.Header["Cache-Control"] != nil {
maxAge, err = ParseMaxAge(resp.Header["Cache-Control"][0])
maxAge, _ = ParseMaxAge(resp.Header["Cache-Control"][0])
}

if maxAge == 0 {
Expand Down
20 changes: 3 additions & 17 deletions graphql/e2e/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,14 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"testing"

"github.com/dgrijalva/jwt-go/v4"

"github.com/dgraph-io/dgraph/graphql/e2e/common"
"github.com/dgraph-io/dgraph/testutil"
"github.com/dgrijalva/jwt-go/v4"
"github.com/google/go-cmp/cmp"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -1368,21 +1365,10 @@ func TestDeepRBACValueCascade(t *testing.T) {
}

func TestMain(m *testing.M) {
schemaFile := "schema.graphql"
schema, err := ioutil.ReadFile(schemaFile)
if err != nil {
panic(err)
}

jsonFile := "test_data.json"
data, err := ioutil.ReadFile(jsonFile)
if err != nil {
panic(errors.Wrapf(err, "Unable to read file %s.", jsonFile))
}

schema, data := common.BootstrapAuthData()
jwtAlgo := []string{jwt.SigningMethodHS256.Name, jwt.SigningMethodRS256.Name}
for _, algo := range jwtAlgo {
authSchema, err := testutil.AppendAuthInfo(schema, algo, "./sample_public_key.pem")
authSchema, err := testutil.AppendAuthInfo(schema, algo, "./sample_public_key.pem", false)
if err != nil {
panic(err)
}
Expand Down
2 changes: 1 addition & 1 deletion graphql/e2e/auth/debug_off/debugoff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func TestMain(m *testing.M) {

jwtAlgo := []string{jwt.SigningMethodHS256.Name, jwt.SigningMethodRS256.Name}
for _, algo := range jwtAlgo {
authSchema, err := testutil.AppendAuthInfo(schema, algo, "../sample_public_key.pem")
authSchema, err := testutil.AppendAuthInfo(schema, algo, "../sample_public_key.pem", false)
if err != nil {
panic(err)
}
Expand Down
6 changes: 6 additions & 0 deletions graphql/e2e/auth/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -585,3 +585,9 @@ type TaskOccurrence @auth(
role: String @search(by: [exact, term, fulltext, regexp])
}

type Todo {
id: ID
owner: String
text: String

}
217 changes: 217 additions & 0 deletions graphql/e2e/auth_closed_by_default/auth_closed_by_default_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/*
* Copyright 2019 Dgraph Labs, Inc. and Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package auth_closed_by_default

import (
"github.com/dgraph-io/dgraph/graphql/e2e/common"
"github.com/dgraph-io/dgraph/testutil"
"github.com/dgrijalva/jwt-go/v4"
"github.com/stretchr/testify/require"
"os"
"testing"
)

const (
graphqlURL = "http://localhost:8180/graphql"
)

type TestCase struct {
name string
query string
variables map[string]interface{}
result string
}

func TestAuthRulesMutationWithClosedByDefaultFlag(t *testing.T) {
testCases := []TestCase{{
name: "Missing JWT from Mutation - type with auth directive",
query: `
mutation addUser($user: AddUserSecretInput!) {
addUserSecret(input: [$user]) {
userSecret {
aSecret
}
}
}`,
variables: map[string]interface{}{"user": &common.UserSecret{
ASecret: "secret1",
OwnedBy: "user1",
}},
result: `{"addUserSecret":null}`,
},
{
name: "Missing JWT from Mutation - type without auth directive",
query: `
mutation addTodo($Todo: AddTodoInput!) {
addTodo(input: [$Todo]) {
todo {
text
owner
}
}
} `,
variables: map[string]interface{}{"Todo": &common.Todo{
Text: "Hi Dgrap team!!",
Owner: "Alice",
}},
result: `{"addTodo":null}`,
},
}

for _, tcase := range testCases {
getUserParams := &common.GraphQLParams{
Query: tcase.query,
Variables: tcase.variables,
}
gqlResponse := getUserParams.ExecuteAsPost(t, graphqlURL)
require.Equal(t, len(gqlResponse.Errors), 1)
require.Contains(t, gqlResponse.Errors[0].Error(),
"a valid JWT is required but was not provided")
require.Equal(t, tcase.result, string(gqlResponse.Data))
}
}

func TestAuthRulesQueryWithClosedByDefaultFlag(t *testing.T) {
testCases := []TestCase{
{name: "Missing JWT from Query - type with auth field",
query: `
query {
queryProject {
name
}
}`,
result: `{"queryProject":[]}`,
},
{name: "Missing JWT from Query - type without auth field",
query: `
query {
queryTodo {
owner
}
}`,
result: `{"queryTodo":[]}`,
},
}

for _, tcase := range testCases {
queryParams := &common.GraphQLParams{
Query: tcase.query,
}
gqlResponse := queryParams.ExecuteAsPost(t, graphqlURL)
require.Equal(t, len(gqlResponse.Errors), 1)
require.Contains(t, gqlResponse.Errors[0].Error(),
"a valid JWT is required but was not provided")
require.Equal(t, tcase.result, string(gqlResponse.Data))
}
}

func TestAuthRulesUpdateWithClosedByDefaultFlag(t *testing.T) {
testCases := []TestCase{{
name: "Missing JWT from Update Mutation - type with auth field",
query: `
mutation ($ids: [ID!]) {
updateIssue(input: {filter: {id: $ids}, set: {random: "test"}}) {
issue (order: {asc: msg}) {
msg
}
}
}
`,
result: `{"updateIssue":null}`,
},
{
name: "Missing JWT from Update Mutation - type without auth field",
query: `
mutation ($ids: [ID!]) {
updateTodo(input: {filter: {id: $ids}, set: {text: "test"}}) {
todo {
text
}
}
}
`,
result: `{"updateTodo":null}`,
}}

for _, tcase := range testCases {
getUserParams := &common.GraphQLParams{
Query: tcase.query,
Variables: map[string]interface{}{"ids": []string{"0x1"}},
}

gqlResponse := getUserParams.ExecuteAsPost(t, graphqlURL)
require.Equal(t, len(gqlResponse.Errors), 1)
require.Contains(t, gqlResponse.Errors[0].Error(),
"a valid JWT is required but was not provided")
require.Equal(t, tcase.result, string(gqlResponse.Data))
}
}

func TestDeleteOrRBACFilter(t *testing.T) {
testCases := []TestCase{{
name: "Missing JWT from delete Mutation- type with auth field",
query: `
mutation($ids: [ID!]) {
deleteComplexLog (filter: { id: $ids}) {
numUids
}
}
`,
result: `{"deleteComplexLog":null}`,
}, {
name: "Missing JWT from delete Mutation - type without auth field",
query: `
mutation($ids: [ID!]) {
deleteTodo (filter: { id: $ids}) {
numUids
}
}
`,
result: `{"deleteTodo":null}`,
}}

for _, tcase := range testCases {
getUserParams := &common.GraphQLParams{
Query: tcase.query,
Variables: map[string]interface{}{"ids": []string{"0x1"}},
}
gqlResponse := getUserParams.ExecuteAsPost(t, graphqlURL)
require.Equal(t, len(gqlResponse.Errors), 1)
require.Contains(t, gqlResponse.Errors[0].Error(),
"a valid JWT is required but was not provided")
require.Equal(t, tcase.result, string(gqlResponse.Data))
}
}

func TestMain(m *testing.M) {
algo := jwt.SigningMethodHS256.Name
schema, data := common.BootstrapAuthData()
authSchema, err := testutil.AppendAuthInfo(schema, algo, "../auth/sample_public_key.pem", true)
if err != nil {
panic(err)
}
common.BootstrapServer(authSchema, data)
// Data is added only in the first iteration, but the schema is added every iteration.
if data != nil {
data = nil
}
exitCode := m.Run()
if exitCode != 0 {
os.Exit(exitCode)
}
os.Exit(0)
}
69 changes: 69 additions & 0 deletions graphql/e2e/auth_closed_by_default/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
version: "3.5"
services:
zero:
image: dgraph/dgraph:latest
container_name: zero1
working_dir: /data/zero1
ports:
- 5180:5180
- 6180:6180
labels:
cluster: test
service: zero1
volumes:
- type: bind
source: $GOPATH/bin
target: /gobin
read_only: true
command: /gobin/dgraph zero -o 100 --logtostderr -v=2 --bindall --expose_trace --profile_mode block --block_rate 10 --my=zero1:5180

alpha:
image: dgraph/dgraph:latest
container_name: alpha1
working_dir: /data/alpha1
volumes:
- type: bind
source: $GOPATH/bin
target: /gobin
read_only: true
ports:
- 8180:8180
- 9180:9180
labels:
cluster: test
service: alpha1
command: /gobin/dgraph alpha --zero=zero1:5180 -o 100 --expose_trace --trace 1.0 --profile_mode block --block_rate 10 --logtostderr -v=3 --whitelist 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 --my=alpha1:7180 --graphql_debug=true

zeroAdmin:
image: dgraph/dgraph:latest
container_name: zeroAdmin
working_dir: /data/zeroAdmin
ports:
- 5280:5280
- 6280:6280
labels:
cluster: admintest
service: zeroAdmin
volumes:
- type: bind
source: $GOPATH/bin
target: /gobin
read_only: true
command: /gobin/dgraph zero -o 200 --logtostderr -v=2 --bindall --expose_trace --profile_mode block --block_rate 10 --my=zeroAdmin:5280

alphaAdmin:
image: dgraph/dgraph:latest
container_name: alphaAdmin
working_dir: /data/alphaAdmin
volumes:
- type: bind
source: $GOPATH/bin
target: /gobin
read_only: true
ports:
- 8280:8280
- 9280:9280
labels:
cluster: admintest
service: alphaAdmin
command: /gobin/dgraph alpha --zero=zeroAdmin:5280 -o 200 --expose_trace --trace 1.0 --profile_mode block --block_rate 10 --logtostderr -v=2 --whitelist 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 --my=alphaAdmin:7280 --graphql_debug=true
Loading