Skip to content

Commit cea049c

Browse files
authored
check if user's provider is in the list of current providers (#176)
1 parent 8da8a5c commit cea049c

2 files changed

Lines changed: 111 additions & 49 deletions

File tree

middleware/auth.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func (a *Authenticator) auth(reqAuth bool) func(http.Handler) http.Handler {
110110
return
111111
}
112112

113-
if claims.Handshake != nil { // handshake in token indicate special use cases, not for login
113+
if claims.Handshake != nil { // handshake in token indicates special use cases, not for login
114114
onError(h, w, r, fmt.Errorf("invalid kind of token"))
115115
return
116116
}
@@ -128,6 +128,13 @@ func (a *Authenticator) auth(reqAuth bool) func(http.Handler) http.Handler {
128128
return
129129
}
130130

131+
// check if user provider is allowed
132+
if !a.isProviderAllowed(claims.User.ID) {
133+
onError(h, w, r, fmt.Errorf("user %s/%s provider is not allowed", claims.User.Name, claims.User.ID))
134+
a.JWTService.Reset(w)
135+
return
136+
}
137+
131138
if a.JWTService.IsExpired(claims) {
132139
if claims, err = a.refreshExpiredToken(w, claims, tkn); err != nil {
133140
a.JWTService.Reset(w)
@@ -146,6 +153,19 @@ func (a *Authenticator) auth(reqAuth bool) func(http.Handler) http.Handler {
146153
return f
147154
}
148155

156+
// isProviderAllowed checks if user provider is allowed, user id looks like "provider_1234567890"
157+
// this check is needed to reject users from providers what are used to be allowed but not anymore.
158+
// Such users made token before the provider was disabled and should not be allowed to login anymore.
159+
func (a *Authenticator) isProviderAllowed(userID string) bool {
160+
userProvider := strings.Split(userID, "_")[0]
161+
for _, p := range a.Providers {
162+
if p.Name() == userProvider {
163+
return true
164+
}
165+
}
166+
return false
167+
}
168+
149169
// refreshExpiredToken makes a new token with passed claims
150170
func (a *Authenticator) refreshExpiredToken(w http.ResponseWriter, claims token.Claims, tkn string) (token.Claims, error) {
151171

middleware/auth_test.go

Lines changed: 90 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,21 @@ import (
1616
"github.com/stretchr/testify/require"
1717

1818
"github.com/go-pkgz/auth/logger"
19+
"github.com/go-pkgz/auth/provider"
1920
"github.com/go-pkgz/auth/token"
2021
)
2122

22-
var testJwtValid = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0X3N5cyIsImV4cCI6Mjc4OTE5MTgyMiwianRpIjoicmFuZG9tIGlkIiwiaXNzIjoicmVtYXJrNDIiLCJuYmYiOjE1MjY4ODQyMjIsInVzZXIiOnsibmFtZSI6Im5hbWUxIiwiaWQiOiJpZDEiLCJwaWN0dXJlIjoiaHR0cDovL2V4YW1wbGUuY29tL3BpYy5wbmciLCJpcCI6IjEyNy4wLjAuMSIsImVtYWlsIjoibWVAZXhhbXBsZS5jb20iLCJhdHRycyI6eyJib29sYSI6dHJ1ZSwic3RyYSI6InN0cmEtdmFsIn19fQ.OWPdibrSSSHuOV3DzzLH5soO6kUcERELL7_GLf7Ja_E"
23+
var testJwtValid = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0X3N5cyIsImV4cCI6Mjc4OTE5MTgyMiwianRpIjoicmFuZG9tIGlkIiwiaXNzIjoicmVtYXJrNDIiLCJuYmYiOjE1MjY4ODQyMjIsInVzZXIiOnsibmFtZSI6Im5hbWUxIiwiaWQiOiJwcm92aWRlcjFfaWQxIiwicGljdHVyZSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9waWMucG5nIiwiaXAiOiIxMjcuMC4wLjEiLCJlbWFpbCI6Im1lQGV4YW1wbGUuY29tIiwiYXR0cnMiOnsiYm9vbGEiOnRydWUsInN0cmEiOiJzdHJhLXZhbCJ9fX0.orBYt_pVA4uvCCw0JMQLla3DA0mpjRTl_U9vT_wtI30"
2324

24-
var testJwtExpired = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0X3N5cyIsImV4cCI6MTE4OTE5MTgyMiwianRpIjoicmFuZG9tIGlkIiwiaXNzIjoicmVtYXJrNDIiLCJuYmYiOjE1MjY4ODQyMjIsInVzZXIiOnsibmFtZSI6Im5hbWUxIiwiaWQiOiJpZDEiLCJwaWN0dXJlIjoiaHR0cDovL2V4YW1wbGUuY29tL3BpYy5wbmciLCJpcCI6IjEyNy4wLjAuMSIsImVtYWlsIjoibWVAZXhhbXBsZS5jb20iLCJhdHRycyI6eyJib29sYSI6dHJ1ZSwic3RyYSI6InN0cmEtdmFsIn19fQ.lJNUjG_9rpAghqy5GwIOrgfQnGDnF3PW5sGzKdijmmg"
25+
var testJwtValidWrongProvider = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0X3N5cyIsImV4cCI6Mjc4OTE5MTgyMiwianRpIjoicmFuZG9tIGlkIiwiaXNzIjoicmVtYXJrNDIiLCJuYmYiOjE1MjY4ODQyMjIsInVzZXIiOnsibmFtZSI6Im5hbWUxIiwiaWQiOiJwcm92aWRlcjNfaWQxIiwicGljdHVyZSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9waWMucG5nIiwiaXAiOiIxMjcuMC4wLjEiLCJlbWFpbCI6Im1lQGV4YW1wbGUuY29tIiwiYXR0cnMiOnsiYm9vbGEiOnRydWUsInN0cmEiOiJzdHJhLXZhbCJ9fX0.p0w7GmXKwujm0ROn0RIACnBwN4KmPcqXDMS9YoFq4jQ"
26+
27+
var testJwtExpired = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0X3N5cyIsImV4cCI6MTE4OTE5MTgyMiwianRpIjoicmFuZG9tIGlkIiwiaXNzIjoicmVtYXJrNDIiLCJuYmYiOjE1MjY4ODQyMjIsInVzZXIiOnsibmFtZSI6Im5hbWUxIiwiaWQiOiJwcm92aWRlcjFfaWQxIiwicGljdHVyZSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9waWMucG5nIiwiaXAiOiIxMjcuMC4wLjEiLCJlbWFpbCI6Im1lQGV4YW1wbGUuY29tIiwiYXR0cnMiOnsiYm9vbGEiOnRydWUsInN0cmEiOiJzdHJhLXZhbCJ9fX0.PlRRc5YA6pvoVOT4NLLOoTwU2Kn3GaTfbjr6j-P6RhA"
2528

2629
var testJwtWithHandshake = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0X3N5cyIsImV4cCI6Mjc4OTE5MTgyMiwianRpIjoicmFuZG9tIGlkIiwiaXNzIjoicmVtYXJrNDIiLCJuYmYiOjE1MjY4ODQyMjIsInVzZXIiOnsibmFtZSI6Im5hbWUxIiwiaWQiOiJpZDEiLCJwaWN0dXJlIjoiaHR0cDovL2V4YW1wbGUuY29tL3BpYy5wbmciLCJpcCI6IjEyNy4wLjAuMSIsImVtYWlsIjoibWVAZXhhbXBsZS5jb20iLCJhdHRycyI6eyJib29sYSI6dHJ1ZSwic3RyYSI6InN0cmEtdmFsIn19LCJoYW5kc2hha2UiOnsic3RhdGUiOiIxMjM0NTYiLCJmcm9tIjoiZnJvbSIsImlkIjoibXlpZC0xMjM0NTYifX0._2X1cAEoxjLA7XuN8xW8V9r7rYfP_m9lSRz_9_UFzac"
2730

2831
var testJwtNoUser = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjI3ODkxOTE4MjIsImp0aSI6InJhbmRvbSBpZCIsImlzcyI6InJlbWFyazQyIiwibmJmIjoxNTI2ODg0MjIyfQ.sBpblkbBRzZsBSPPNrTWqA5h7h54solrw5L4IypJT_o"
2932

30-
var testJwtWithRole = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0X3N5cyIsImV4cCI6Mjc4OTE5MTgyMiwianRpIjoicmFuZG9tIGlkIiwiaXNzIjoicmVtYXJrNDIiLCJuYmYiOjE1MjY4ODQyMjIsInVzZXIiOnsibmFtZSI6Im5hbWUxIiwiaWQiOiJpZDEiLCJwaWN0dXJlIjoiaHR0cDovL2V4YW1wbGUuY29tL3BpYy5wbmciLCJpcCI6IjEyNy4wLjAuMSIsImVtYWlsIjoibWVAZXhhbXBsZS5jb20iLCJhdHRycyI6eyJib29sYSI6dHJ1ZSwic3RyYSI6InN0cmEtdmFsIn0sInJvbGUiOiJlbXBsb3llZSJ9fQ.VLW4_LUDZq_eFc9F1Zx1lbv2Whic2VHy6C0dJ5azL8A"
33+
var testJwtWithRole = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0X3N5cyIsImV4cCI6Mjc4OTE5MTgyMiwianRpIjoicmFuZG9tIGlkIiwiaXNzIjoicmVtYXJrNDIiLCJuYmYiOjE1MjY4ODQyMjIsInVzZXIiOnsibmFtZSI6Im5hbWUxIiwiaWQiOiJwcm92aWRlcjFfaWQxIiwicGljdHVyZSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9waWMucG5nIiwiaXAiOiIxMjcuMC4wLjEiLCJlbWFpbCI6Im1lQGV4YW1wbGUuY29tIiwiYXR0cnMiOnsiYm9vbGEiOnRydWUsInN0cmEiOiJzdHJhLXZhbCJ9LCJyb2xlIjoiZW1wbG95ZWUifX0.o95raB0aNl2TWUs43Tu6xyX5Y3Fa5wv6_6RFJuN-d6g"
3134

3235
func TestAuthJWTCookie(t *testing.T) {
3336
a := makeTestAuth(t)
@@ -36,7 +39,7 @@ func TestAuthJWTCookie(t *testing.T) {
3639
handler := func(w http.ResponseWriter, r *http.Request) {
3740
u, err := token.GetUserInfo(r)
3841
assert.NoError(t, err)
39-
assert.Equal(t, token.User{Name: "name1", ID: "id1", Picture: "http://example.com/pic.png",
42+
assert.Equal(t, token.User{Name: "name1", ID: "provider1_id1", Picture: "http://example.com/pic.png",
4043
IP: "127.0.0.1", Email: "me@example.com", Audience: "test_sys",
4144
Attributes: map[string]interface{}{"boola": true, "stra": "stra-val"}}, u)
4245
w.WriteHeader(201)
@@ -45,40 +48,61 @@ func TestAuthJWTCookie(t *testing.T) {
4548
server := httptest.NewServer(mux)
4649
defer server.Close()
4750

51+
client := &http.Client{Timeout: 5 * time.Second}
4852
expiration := int(365 * 24 * time.Hour.Seconds()) //nolint
49-
req, err := http.NewRequest("GET", server.URL+"/auth", http.NoBody)
50-
require.Nil(t, err)
51-
req.AddCookie(&http.Cookie{Name: "JWT", Value: testJwtValid, HttpOnly: true, Path: "/", MaxAge: expiration, Secure: false})
52-
req.Header.Add("X-XSRF-TOKEN", "random id")
5353

54-
client := &http.Client{Timeout: 5 * time.Second}
55-
resp, err := client.Do(req)
56-
require.NoError(t, err)
57-
assert.Equal(t, 201, resp.StatusCode, "valid token user")
54+
t.Run("valid token", func(t *testing.T) {
55+
req, err := http.NewRequest("GET", server.URL+"/auth", http.NoBody)
56+
require.Nil(t, err)
57+
req.AddCookie(&http.Cookie{Name: "JWT", Value: testJwtValid, HttpOnly: true, Path: "/", MaxAge: expiration, Secure: false})
58+
req.Header.Add("X-XSRF-TOKEN", "random id")
5859

59-
req, err = http.NewRequest("GET", server.URL+"/auth", http.NoBody)
60-
require.Nil(t, err)
61-
req.AddCookie(&http.Cookie{Name: "JWT", Value: testJwtValid, HttpOnly: true, Path: "/", MaxAge: expiration, Secure: false})
62-
req.Header.Add("X-XSRF-TOKEN", "wrong id")
63-
resp, err = client.Do(req)
64-
require.NoError(t, err)
65-
assert.Equal(t, 401, resp.StatusCode, "xsrf mismatch")
60+
resp, err := client.Do(req)
61+
require.NoError(t, err)
62+
assert.Equal(t, 201, resp.StatusCode, "valid token user")
63+
})
6664

67-
req, err = http.NewRequest("GET", server.URL+"/auth", http.NoBody)
68-
require.Nil(t, err)
69-
req.AddCookie(&http.Cookie{Name: "JWT", Value: testJwtExpired, HttpOnly: true, Path: "/", MaxAge: expiration, Secure: false})
70-
req.Header.Add("X-XSRF-TOKEN", "random id")
71-
resp, err = client.Do(req)
72-
require.NoError(t, err)
73-
assert.Equal(t, 201, resp.StatusCode, "token expired and refreshed")
65+
t.Run("valid token, wrong provider", func(t *testing.T) {
66+
req, err := http.NewRequest("GET", server.URL+"/auth", http.NoBody)
67+
require.Nil(t, err)
68+
req.AddCookie(&http.Cookie{Name: "JWT", Value: testJwtValidWrongProvider, HttpOnly: true, Path: "/",
69+
MaxAge: expiration, Secure: false})
70+
req.Header.Add("X-XSRF-TOKEN", "random id")
7471

75-
req, err = http.NewRequest("GET", server.URL+"/auth", http.NoBody)
76-
require.Nil(t, err)
77-
req.AddCookie(&http.Cookie{Name: "JWT", Value: testJwtNoUser, HttpOnly: true, Path: "/", MaxAge: expiration, Secure: false})
78-
req.Header.Add("X-XSRF-TOKEN", "random id")
79-
resp, err = client.Do(req)
80-
require.NoError(t, err)
81-
assert.Equal(t, 401, resp.StatusCode, "no user info in the token")
72+
resp, err := client.Do(req)
73+
require.NoError(t, err)
74+
assert.Equal(t, 401, resp.StatusCode, "user name1/provider3_id1 provider is not allowed")
75+
})
76+
77+
t.Run("xsrf mismatch", func(t *testing.T) {
78+
req, err := http.NewRequest("GET", server.URL+"/auth", http.NoBody)
79+
require.Nil(t, err)
80+
req.AddCookie(&http.Cookie{Name: "JWT", Value: testJwtValid, HttpOnly: true, Path: "/", MaxAge: expiration, Secure: false})
81+
req.Header.Add("X-XSRF-TOKEN", "wrong id")
82+
resp, err := client.Do(req)
83+
require.NoError(t, err)
84+
assert.Equal(t, 401, resp.StatusCode, "xsrf mismatch")
85+
})
86+
87+
t.Run("token expired and refreshed", func(t *testing.T) {
88+
req, err := http.NewRequest("GET", server.URL+"/auth", http.NoBody)
89+
require.Nil(t, err)
90+
req.AddCookie(&http.Cookie{Name: "JWT", Value: testJwtExpired, HttpOnly: true, Path: "/", MaxAge: expiration, Secure: false})
91+
req.Header.Add("X-XSRF-TOKEN", "random id")
92+
resp, err := client.Do(req)
93+
require.NoError(t, err)
94+
assert.Equal(t, 201, resp.StatusCode, "token expired and refreshed")
95+
})
96+
97+
t.Run("no user info in the token", func(t *testing.T) {
98+
req, err := http.NewRequest("GET", server.URL+"/auth", http.NoBody)
99+
require.Nil(t, err)
100+
req.AddCookie(&http.Cookie{Name: "JWT", Value: testJwtNoUser, HttpOnly: true, Path: "/", MaxAge: expiration, Secure: false})
101+
req.Header.Add("X-XSRF-TOKEN", "random id")
102+
resp, err := client.Do(req)
103+
require.NoError(t, err)
104+
assert.Equal(t, 401, resp.StatusCode, "no user info in the token")
105+
})
82106
}
83107

84108
func TestAuthJWTHeader(t *testing.T) {
@@ -87,19 +111,32 @@ func TestAuthJWTHeader(t *testing.T) {
87111
defer server.Close()
88112

89113
client := &http.Client{Timeout: 5 * time.Second}
90-
req, err := http.NewRequest("GET", server.URL+"/auth", http.NoBody)
91-
require.Nil(t, err)
92-
req.Header.Add("X-JWT", testJwtValid)
93-
resp, err := client.Do(req)
94-
require.NoError(t, err)
95-
assert.Equal(t, 201, resp.StatusCode, "valid token user")
114+
t.Run("valid token", func(t *testing.T) {
115+
req, err := http.NewRequest("GET", server.URL+"/auth", http.NoBody)
116+
require.Nil(t, err)
117+
req.Header.Add("X-JWT", testJwtValid)
118+
resp, err := client.Do(req)
119+
require.NoError(t, err)
120+
assert.Equal(t, 201, resp.StatusCode, "valid token user")
121+
})
96122

97-
req, err = http.NewRequest("GET", server.URL+"/auth", http.NoBody)
98-
require.Nil(t, err)
99-
req.Header.Add("X-JWT", testJwtExpired)
100-
resp, err = client.Do(req)
101-
require.NoError(t, err)
102-
assert.Equal(t, 401, resp.StatusCode, "token expired")
123+
t.Run("valid token, wrong provider", func(t *testing.T) {
124+
req, err := http.NewRequest("GET", server.URL+"/auth", http.NoBody)
125+
require.Nil(t, err)
126+
req.Header.Add("X-JWT", testJwtValidWrongProvider)
127+
resp, err := client.Do(req)
128+
require.NoError(t, err)
129+
assert.Equal(t, 401, resp.StatusCode, "wrong provider")
130+
})
131+
132+
t.Run("token expired", func(t *testing.T) {
133+
req, err := http.NewRequest("GET", server.URL+"/auth", http.NoBody)
134+
require.Nil(t, err)
135+
req.Header.Add("X-JWT", testJwtExpired)
136+
resp, err := client.Do(req)
137+
require.NoError(t, err)
138+
assert.Equal(t, 401, resp.StatusCode, "token expired")
139+
})
103140
}
104141

105142
func TestAuthJWTRefresh(t *testing.T) {
@@ -177,7 +214,7 @@ func TestAuthJWTRefreshConcurrentWithCache(t *testing.T) {
177214
// make another expired token
178215
c, err := a.JWTService.Parse(testJwtExpired)
179216
require.NoError(t, err)
180-
c.User.ID = "other ID"
217+
c.User.ID = "provider1_other ID"
181218
tkSvc := a.JWTService.(*token.Service)
182219
tkn, err := tkSvc.Token(c)
183220
require.NoError(t, err)
@@ -413,7 +450,7 @@ func TestRBAC(t *testing.T) {
413450
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
414451
u, err := token.GetUserInfo(r)
415452
assert.NoError(t, err)
416-
assert.Equal(t, token.User{Name: "name1", ID: "id1", Picture: "http://example.com/pic.png",
453+
assert.Equal(t, token.User{Name: "name1", ID: "provider1_id1", Picture: "http://example.com/pic.png",
417454
IP: "127.0.0.1", Email: "me@example.com", Audience: "test_sys",
418455
Attributes: map[string]interface{}{"boola": true, "stra": "stra-val"},
419456
Role: "employee"}, u)
@@ -438,7 +475,7 @@ func TestRBAC(t *testing.T) {
438475
require.NoError(t, err)
439476
assert.Equal(t, 201, resp.StatusCode, "valid token user")
440477

441-
// employee route only, token without employee role
478+
// employee route only, token without an employee role
442479
expiration = int(365 * 24 * time.Hour.Seconds()) //nolint
443480
req, err = http.NewRequest("GET", server.URL+"/authForEmployees", http.NoBody)
444481
require.Nil(t, err)
@@ -480,6 +517,7 @@ func makeTestMux(_ *testing.T, a *Authenticator, required bool) http.Handler {
480517
}
481518

482519
func makeTestAuth(_ *testing.T) Authenticator {
520+
483521
j := token.NewService(token.Opts{
484522
SecretReader: token.SecretFunc(func(string) (string, error) { return "xyz 12345", nil }),
485523
SecureCookies: false,
@@ -497,6 +535,10 @@ func makeTestAuth(_ *testing.T) Authenticator {
497535
JWTService: j,
498536
Validator: token.ValidatorFunc(func(token string, claims token.Claims) bool { return true }),
499537
L: logger.Std,
538+
Providers: []provider.Service{
539+
{Provider: provider.DirectHandler{ProviderName: "provider1"}},
540+
{Provider: provider.DirectHandler{ProviderName: "provider2"}},
541+
},
500542
}
501543
}
502544

0 commit comments

Comments
 (0)