Skip to content

Commit b6bbcc0

Browse files
authored
test: adding e2e test for oidc authentication flow (#273)
Signed-off-by: Kush Sharma <thekushsharma@gmail.com>
1 parent c211953 commit b6bbcc0

File tree

9 files changed

+194
-12
lines changed

9 files changed

+194
-12
lines changed

.goreleaser.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ dockers:
5656
dockerfile: Dockerfile
5757
image_templates:
5858
- "docker.io/raystack/{{.ProjectName}}:latest"
59-
- "docker.io/raystack/{{.ProjectName}}:{{ .Version }}"
59+
- "docker.io/raystack/{{.ProjectName}}:{{ .Tag }}"
6060
- "docker.io/raystack/{{.ProjectName}}:{{ .Tag }}-amd64"
6161
- goos: linux
6262
goarch: arm64

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Shield is a cloud native role-based authorization aware system. With Shield, you
1212

1313
## Key Features
1414

15-
Discover why users choose Shield as their authorization proxy
15+
Discover why users choose Shield as their authorization server
1616

1717
- **Authentication**: It supports multiple authentication strategies like Email OTP, Social Login(Google/Github) for human users and API keys, RSA JWT based for machine users.
1818
- **Authorization**: Policies bind a user to its access level. It assigns various roles to users/groups that determine their access to various resources
@@ -87,7 +87,7 @@ docker pull raystack/shield:latest
8787
To pull a specific version:
8888

8989
```
90-
docker pull raystack/shield:v0.3.2
90+
docker pull raystack/shield:v0.6.2-arm64
9191
```
9292

9393
If you like to have a shell alias that runs the latest version of shield from docker whenever you type `shield`:

cmd/root.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import (
1010
func New(cliConfig *Config) *cli.Command {
1111
var cmd = &cli.Command{
1212
Use: "shield <command> <subcommand> [flags]",
13-
Short: "A cloud native role-based authorization aware reverse-proxy service",
13+
Short: "A cloud native role-based authorization server",
1414
Long: heredoc.Doc(`
15-
A cloud native role-based authorization aware reverse-proxy service.`),
15+
A cloud native role-based authorization server.`),
1616
SilenceUsage: true,
1717
SilenceErrors: true,
1818
Annotations: map[string]string{

docs/docs/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ $ docker pull raystack/shield
5656
To pull a specific version:
5757

5858
```sh
59-
$ docker pull raystack/shield:v0.5.1
59+
$ docker pull raystack/shield:v0.6.2-arm64
6060
```
6161

6262
To run the docker image with minimum configurations:

docs/docs/introduction.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ Shield by Raystack is a role-based cloud-native user management system and autho
1717

1818
Here are the steps to work with Shield.
1919

20-
1. Configure policies: This step involves definition of resource types that will exist in the connected backend. User can also configure the roles and permissions that exist.
21-
22-
2. Configure rules: This step involves defining the authorization(via authz middleware) and resource creation(via hook) for each path in the backend.
23-
24-
3. Making Shield proxy request: User can now hit at the shield server followed by the url path.
20+
1. Configure shield: Shield has various tuning parameters that can be configured to suit the needs of the organization.
21+
This includes the configuration of the database, OIDC provider, email provider, etc.
22+
2. Configure policies: This step involves definition of resource types that will exist in the connected backend.
23+
A resource is a protected backend service for example an order management service or user picture library. User can
24+
also configure the roles and permissions to be assigned to the users.
25+
3. Connecting frontend: Shield provides a set of APIs that can be used by the frontend to authenticate, authorize and
26+
manage users. The frontend can be a web application, mobile application or any other application that can make HTTP
27+
requests.
2528

2629
## Key Features
2730

@@ -47,7 +50,7 @@ You can manage relation creation, checking authorization on a resource and much
4750

4851
### Admin Portal
4952

50-
Besides HTTP APIs and CLI tool, Shield provides an provides an out-of-the-box UI for admins to configure SSO for the clients and manage roles, users, groups and organisations in one place.
53+
Besides HTTP APIs and CLI tool, Shield provides an out-of-the-box UI for admins to configure SSO for the clients and manage roles, users, groups and organisations in one place.
5154

5255
## Where to go from here
5356

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ require (
3030
github.com/mcuadros/go-defaults v1.2.0
3131
github.com/mitchellh/mapstructure v1.5.0
3232
github.com/newrelic/go-agent v3.20.2+incompatible
33+
github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282
3334
github.com/ory/dockertest v3.3.5+incompatible
3435
github.com/ory/dockertest/v3 v3.9.1
3536
github.com/pkg/errors v0.9.1
@@ -56,8 +57,10 @@ require (
5657

5758
require (
5859
github.com/felixge/httpsnoop v1.0.3 // indirect
60+
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
5961
github.com/oklog/run v1.1.0 // indirect
6062
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
63+
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
6164
)
6265

6366
require (

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
11361136
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
11371137
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
11381138
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
1139+
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
11391140
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
11401141
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
11411142
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
@@ -1756,6 +1757,8 @@ github.com/newrelic/go-agent v3.20.2+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HD
17561757
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
17571758
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
17581759
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
1760+
github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282 h1:TQMyrpijtkFyXpNI3rY5hsZQZw+paiH+BfAlsb81HBY=
1761+
github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282/go.mod h1:rW25Kyd08Wdn3UVn0YBsDTSvReu0jqpmJKzxITPSjks=
17591762
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
17601763
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
17611764
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
@@ -2297,6 +2300,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
22972300
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
22982301
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
22992302
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
2303+
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
23002304
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
23012305
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
23022306
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -3124,6 +3128,8 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
31243128
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
31253129
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
31263130
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
3131+
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
3132+
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
31273133
gopkg.in/telebot.v3 v3.0.0/go.mod h1:7rExV8/0mDDNu9epSrDm/8j22KLaActH1Tbee6YjzWg=
31283134
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
31293135
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=

internal/api/v1beta1/user.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ func transformUserToPB(usr user.User) (*shieldv1beta1.User, error) {
498498
Email: usr.Email,
499499
Name: usr.Name,
500500
Metadata: metaData,
501+
State: usr.State.String(),
501502
CreatedAt: timestamppb.New(usr.CreatedAt),
502503
UpdatedAt: timestamppb.New(usr.UpdatedAt),
503504
}, nil
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package e2e_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"net/url"
8+
"os"
9+
"path"
10+
"testing"
11+
12+
"github.com/golang/protobuf/jsonpb"
13+
14+
"github.com/oauth2-proxy/mockoidc"
15+
"github.com/raystack/shield/config"
16+
"github.com/raystack/shield/core/authenticate"
17+
"github.com/raystack/shield/pkg/logger"
18+
"github.com/raystack/shield/pkg/server"
19+
shieldv1beta1 "github.com/raystack/shield/proto/v1beta1"
20+
"github.com/raystack/shield/test/e2e/testbench"
21+
"github.com/stretchr/testify/suite"
22+
)
23+
24+
type AuthenticationRegressionTestSuite struct {
25+
suite.Suite
26+
testBench *testbench.TestBench
27+
apiPort int
28+
mockOIDCServer *mockoidc.MockOIDC
29+
callbackPort int
30+
}
31+
32+
func (s *AuthenticationRegressionTestSuite) SetupSuite() {
33+
wd, err := os.Getwd()
34+
s.Require().Nil(err)
35+
testDataPath := path.Join("file://", wd, fixturesDir)
36+
37+
apiPort, err := testbench.GetFreePort()
38+
s.Require().NoError(err)
39+
grpcPort, err := testbench.GetFreePort()
40+
s.Require().NoError(err)
41+
s.apiPort = apiPort
42+
callbackPort, err := testbench.GetFreePort()
43+
s.Require().NoError(err)
44+
s.callbackPort = callbackPort
45+
46+
s.mockOIDCServer, err = mockoidc.Run()
47+
s.Require().NoError(err)
48+
49+
// mock callback host
50+
51+
appConfig := &config.Shield{
52+
Log: logger.Config{
53+
Level: "error",
54+
},
55+
App: server.Config{
56+
Host: "localhost",
57+
Port: apiPort,
58+
GRPC: server.GRPCConfig{
59+
Port: grpcPort,
60+
MaxRecvMsgSize: 2 << 10,
61+
MaxSendMsgSize: 2 << 10,
62+
},
63+
ResourcesConfigPath: path.Join(testDataPath, "resource"),
64+
Authentication: authenticate.Config{
65+
Session: authenticate.SessionConfig{
66+
HashSecretKey: "hash-secret-should-be-32-chars--",
67+
BlockSecretKey: "hash-secret-should-be-32-chars--",
68+
},
69+
Token: authenticate.TokenConfig{
70+
RSAPath: "testdata/jwks.json",
71+
Issuer: "shield",
72+
},
73+
OIDCConfig: map[string]authenticate.OIDCConfig{
74+
"mock": {
75+
ClientID: s.mockOIDCServer.Config().ClientID,
76+
ClientSecret: s.mockOIDCServer.Config().ClientSecret,
77+
IssuerUrl: s.mockOIDCServer.Issuer(),
78+
},
79+
},
80+
OIDCCallbackHost: fmt.Sprintf("http://localhost:%d/callback", s.callbackPort),
81+
},
82+
},
83+
}
84+
85+
s.testBench, err = testbench.Init(appConfig)
86+
s.Require().NoError(err)
87+
}
88+
89+
func (s *AuthenticationRegressionTestSuite) TearDownSuite() {
90+
err := s.testBench.Close()
91+
s.Require().NoError(err)
92+
err = s.mockOIDCServer.Shutdown()
93+
s.Require().NoError(err)
94+
}
95+
96+
func (s *AuthenticationRegressionTestSuite) TestUserSession() {
97+
ctx := context.Background()
98+
s.Run("1. return authenticate strategies of oidc", func() {
99+
authStrategyResp, err := s.testBench.Client.ListAuthStrategies(ctx, &shieldv1beta1.ListAuthStrategiesRequest{})
100+
s.Assert().NoError(err)
101+
s.Assert().Equal("mock", authStrategyResp.GetStrategies()[0].GetName())
102+
})
103+
s.Run("2. authenticate a user successfully using oidc and create a session via cookies", func() {
104+
// start registration flow
105+
authResp, err := s.testBench.Client.Authenticate(ctx, &shieldv1beta1.AuthenticateRequest{
106+
StrategyName: "mock",
107+
Redirect: false,
108+
ReturnTo: "",
109+
Email: mockoidc.DefaultUser().Email,
110+
})
111+
s.Assert().NoError(err)
112+
s.Assert().NotNil(authResp.Endpoint)
113+
114+
// mock oidc code
115+
parsedEndpoint, err := url.Parse(authResp.Endpoint)
116+
s.Assert().NoError(err)
117+
mockAuth0Code := "012345"
118+
s.mockOIDCServer.QueueCode(mockAuth0Code)
119+
120+
// prepare mock callback server
121+
callbackMux := http.NewServeMux()
122+
callbackMux.Handle("/callback", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
123+
code := r.URL.Query().Get("code")
124+
state := r.URL.Query().Get("state")
125+
s.Assert().Equal(mockAuth0Code, code)
126+
s.Assert().Equal(parsedEndpoint.Query().Get("state"), state)
127+
w.WriteHeader(http.StatusOK)
128+
}))
129+
srv := &http.Server{
130+
Addr: fmt.Sprintf("localhost:%d", s.callbackPort),
131+
Handler: callbackMux,
132+
}
133+
// clean up callback server
134+
defer srv.Shutdown(ctx)
135+
go func() {
136+
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
137+
s.Assert().NoError(err)
138+
}
139+
}()
140+
141+
// start session in oidc server
142+
endpointRes, err := http.Get(authResp.Endpoint)
143+
s.Assert().NoError(err)
144+
s.Assert().Equal(http.StatusOK, endpointRes.StatusCode)
145+
146+
// callback to shield and get valid cookies
147+
authCallbackFinalResp, err := http.Get(fmt.Sprintf("http://localhost:%d/v1beta1/auth/callback?code=%s&state=%s",
148+
s.apiPort, mockAuth0Code, parsedEndpoint.Query().Get("state")))
149+
s.Assert().NoError(err)
150+
s.Assert().Equal(http.StatusOK, authCallbackFinalResp.StatusCode)
151+
s.Assert().Equal("sid", authCallbackFinalResp.Cookies()[0].Name)
152+
153+
// verify if session is created
154+
getUserReq, _ := http.NewRequest("GET", fmt.Sprintf("http://localhost:%d/v1beta1/users/self", s.apiPort), nil)
155+
getUserReq.AddCookie(authCallbackFinalResp.Cookies()[0])
156+
157+
userResp, err := http.DefaultClient.Do(getUserReq)
158+
s.Assert().NoError(err)
159+
s.Assert().Equal(http.StatusOK, userResp.StatusCode)
160+
161+
user := &shieldv1beta1.GetCurrentUserResponse{}
162+
s.Assert().NoError(jsonpb.Unmarshal(userResp.Body, user))
163+
s.Assert().Equal(mockoidc.DefaultUser().Email, user.GetUser().Email)
164+
})
165+
}
166+
167+
func TestEndToEndAuthenticationRegressionTestSuite(t *testing.T) {
168+
suite.Run(t, new(AuthenticationRegressionTestSuite))
169+
}

0 commit comments

Comments
 (0)