Skip to content

Commit 104ece4

Browse files
authored
Merge pull request #419 from MadAppGang/feature/login-scopes-refactoring
Feature/login scopes refactoring
2 parents d77fd4f + 1f4a461 commit 104ece4

File tree

29 files changed

+149
-82
lines changed

29 files changed

+149
-82
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*.out
1515
vendor
1616
db.db
17+
db_plugin.db
1718
shared-local-instance.db
1819
debug
1920
*__debug_bin

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ build:
3939
go build -o ./identifo
4040

4141
lint:
42-
golangci-lint run -D deadcode,errcheck,unused,varcheck,govet
42+
golangci-lint run -D errcheck,unused,govet
4343

4444
build_admin_panel:
4545
rm -rf static/admin_panel

cmd/config-boltdb.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ storage:
1111
type: boltdb
1212
boltdb:
1313
path: ./db.db
14-
userStorage: *storage_settings
14+
userStorage:
15+
type: plugin
16+
plugin:
17+
cmd: ./plugins/bin/bolt-user-storage
18+
params: { "path": "./db_plugin.db" }
19+
redirectStd: true
1520
tokenStorage: *storage_settings
1621
tokenBlacklist: *storage_settings
1722
verificationCodeStorage: *storage_settings

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ require (
1616
github.com/google/uuid v1.3.0
1717
github.com/gorilla/mux v1.8.0
1818
github.com/gorilla/sessions v1.2.1
19+
github.com/hashicorp/go-hclog v0.14.1
1920
github.com/hashicorp/go-plugin v1.4.5
2021
github.com/hummerd/httpdump v0.9.1
2122
github.com/joho/godotenv v1.4.0
@@ -70,7 +71,6 @@ require (
7071
github.com/golang/snappy v0.0.1 // indirect
7172
github.com/google/go-cmp v0.5.9 // indirect
7273
github.com/gorilla/securecookie v1.1.1 // indirect
73-
github.com/hashicorp/go-hclog v0.14.1 // indirect
7474
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
7575
github.com/jmespath/go-jmespath v0.4.0 // indirect
7676
github.com/klauspost/compress v1.13.6 // indirect

impersonation/plugin/provider.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import (
55
"os/exec"
66
"time"
77

8+
"github.com/hashicorp/go-hclog"
89
"github.com/hashicorp/go-plugin"
910
grpcShared "github.com/madappgang/identifo/v2/impersonation/grpc/shared"
1011
"github.com/madappgang/identifo/v2/impersonation/plugin/shared"
1112
"github.com/madappgang/identifo/v2/model"
1213
)
1314

1415
func NewImpersonationProvider(settings model.PluginSettings, timeout time.Duration) (model.ImpersonationProvider, error) {
15-
var err error
1616
params := []string{}
1717
for k, v := range settings.Params {
1818
params = append(params, "-"+k)
@@ -24,6 +24,10 @@ func NewImpersonationProvider(settings model.PluginSettings, timeout time.Durati
2424
Plugins: shared.PluginMap,
2525
Cmd: exec.Command(settings.Cmd, params...),
2626
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
27+
Logger: hclog.New(&hclog.LoggerOptions{
28+
Level: hclog.Debug,
29+
JSONFormat: true,
30+
}),
2731
}
2832

2933
if settings.RedirectStd {

jwt/service/jwt_token_service.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ func (ts *JWTokenService) ValidateTokenString(tstr string, v jwtValidator.Valida
217217
// NewAccessToken creates new access token for user.
218218
func (ts *JWTokenService) NewAccessToken(
219219
user model.User,
220-
scopes []string,
220+
scopes model.AllowedScopesSet,
221221
app model.AppData,
222222
requireTFA bool,
223223
tokenPayload map[string]interface{},
@@ -235,10 +235,11 @@ func (ts *JWTokenService) NewAccessToken(
235235
payload[PayloadName] = user.Username
236236
}
237237

238-
tokenType := model.TokenTypeAccess
238+
scopesStr := scopes.String()
239239
if requireTFA {
240-
scopes = []string{model.TokenTypeTFAPreauth}
240+
scopesStr = model.TokenTypeTFAPreauth
241241
}
242+
242243
if len(tokenPayload) > 0 {
243244
for k, v := range tokenPayload {
244245
payload[k] = v
@@ -253,9 +254,9 @@ func (ts *JWTokenService) NewAccessToken(
253254
}
254255

255256
claims := &model.Claims{
256-
Scopes: strings.Join(scopes, " "),
257+
Scopes: scopesStr,
257258
Payload: payload,
258-
Type: tokenType,
259+
Type: model.TokenTypeAccess,
259260
StandardClaims: jwt.StandardClaims{
260261
ExpiresAt: (now + lifespan),
261262
Issuer: ts.issuer,
@@ -278,22 +279,27 @@ func (ts *JWTokenService) NewAccessToken(
278279
}
279280

280281
// NewRefreshToken creates new refresh token.
281-
func (ts *JWTokenService) NewRefreshToken(u model.User, scopes []string, app model.AppData) (model.Token, error) {
282+
func (ts *JWTokenService) NewRefreshToken(
283+
user model.User,
284+
scopes model.AllowedScopesSet,
285+
app model.AppData,
286+
) (model.Token, error) {
282287
if !app.Active || !app.Offline {
283288
return nil, ErrInvalidApp
284289
}
290+
285291
// no offline request
286-
if !model.SliceContains(scopes, model.OfflineScope) {
292+
if !scopes.Contains(model.OfflineScope) {
287293
return nil, ErrInvalidOfflineScope
288294
}
289295

290-
if !u.Active {
296+
if !user.Active {
291297
return nil, ErrInvalidUser
292298
}
293299

294300
payload := make(map[string]interface{})
295301
if model.SliceContains(app.TokenPayload, PayloadName) {
296-
payload[PayloadName] = u.Username
302+
payload[PayloadName] = user.Username
297303
}
298304
now := ijwt.TimeFunc().Unix()
299305

@@ -303,13 +309,13 @@ func (ts *JWTokenService) NewRefreshToken(u model.User, scopes []string, app mod
303309
}
304310

305311
claims := &model.Claims{
306-
Scopes: strings.Join(scopes, " "),
312+
Scopes: scopes.String(),
307313
Payload: payload,
308314
Type: model.TokenTypeRefresh,
309315
StandardClaims: jwt.StandardClaims{
310316
ExpiresAt: (now + lifespan),
311317
Issuer: ts.issuer,
312-
Subject: u.ID,
318+
Subject: user.ID,
313319
Audience: app.ID,
314320
IssuedAt: now,
315321
},
@@ -338,7 +344,10 @@ func (ts *JWTokenService) NewRefreshToken(u model.User, scopes []string, app mod
338344
}
339345

340346
// RefreshAccessToken issues new access token for provided refresh token.
341-
func (ts *JWTokenService) RefreshAccessToken(refreshToken model.Token, tokenPayload map[string]interface{}) (model.Token, error) {
347+
func (ts *JWTokenService) RefreshAccessToken(
348+
refreshToken model.Token,
349+
tokenPayload map[string]interface{},
350+
) (model.Token, error) {
342351
rt, ok := refreshToken.(*model.JWToken)
343352
if !ok || rt == nil {
344353
return nil, model.ErrTokenInvalid

jwt/token_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ func TestNewToken(t *testing.T) {
113113
}, "password", "admin", false)
114114
scopes := []string{"scope1", "scope2"}
115115
tokenPayload := []string{"name"}
116+
116117
app := model.AppData{
117118
ID: "123456",
118119
Secret: "1",
@@ -138,7 +139,10 @@ func TestNewToken(t *testing.T) {
138139
RolesBlacklist: []string{},
139140
NewUserDefaultRole: "",
140141
}
141-
token, err := tokenService.NewAccessToken(user, scopes, app, false, nil)
142+
143+
allowedScopes := model.AllowedScopes(scopes, scopes, false)
144+
145+
token, err := tokenService.NewAccessToken(user, allowedScopes, app, false, nil)
142146
assert.NoError(t, err)
143147

144148
tokenString, err := tokenService.String(token)

model/slice.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@ func SliceIntersect(a, b []string) []string {
1818
}
1919

2020
func SliceContains(s []string, e string) bool {
21+
el := strings.TrimSpace(e)
22+
2123
for _, a := range s {
22-
if strings.TrimSpace(strings.ToLower(a)) == strings.TrimSpace(strings.ToLower(e)) {
24+
if strings.EqualFold(strings.TrimSpace(a), el) {
2325
return true
2426
}
27+
2528
}
29+
2630
return false
2731
}
2832

model/token.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package model
22

33
import (
4+
"strings"
45
"time"
56

67
jwt "github.com/golang-jwt/jwt/v4"
@@ -182,15 +183,31 @@ type Claims struct {
182183
// Full example of how to use JWT tokens:
183184
// https://github.com/form3tech-oss/jwt-go/blob/master/cmd/jwt/app.go
184185

185-
func AllowedScopes(requestedScopes, userScopes []string, isOffline bool) []string {
186-
scopes := []string{}
186+
// This type is needed for guard against passing unchecked scopes to the token.
187+
// Do not convert user provided scopes to this type directly.
188+
type AllowedScopesSet struct {
189+
scopes []string
190+
}
191+
192+
func (a AllowedScopesSet) String() string {
193+
return strings.Join(a.scopes, " ")
194+
}
195+
196+
func (a AllowedScopesSet) Scopes() []string {
197+
return a.scopes
198+
}
199+
200+
func (a AllowedScopesSet) Contains(scope string) bool {
201+
return SliceContains(a.scopes, scope)
202+
}
203+
204+
func AllowedScopes(requestedScopes, userScopes []string, isOffline bool) AllowedScopesSet {
187205
// if we requested any scope, let's provide all the scopes user has and requested
188-
if len(requestedScopes) > 0 {
189-
scopes = SliceIntersect(requestedScopes, userScopes)
190-
}
191-
if SliceContains(requestedScopes, "offline") && isOffline {
192-
scopes = append(scopes, "offline")
206+
scopes := SliceIntersect(requestedScopes, userScopes)
207+
208+
if SliceContains(requestedScopes, OfflineScope) && isOffline {
209+
scopes = append(scopes, OfflineScope)
193210
}
194211

195-
return scopes
212+
return AllowedScopesSet{scopes}
196213
}

model/token_service.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ const (
77

88
// TokenService is an abstract token manager.
99
type TokenService interface {
10-
NewAccessToken(u User, scopes []string, app AppData, requireTFA bool, tokenPayload map[string]interface{}) (Token, error)
11-
NewRefreshToken(u User, scopes []string, app AppData) (Token, error)
10+
NewAccessToken(u User, scopes AllowedScopesSet, app AppData, requireTFA bool, tokenPayload map[string]interface{}) (Token, error)
11+
NewRefreshToken(u User, scopes AllowedScopesSet, app AppData) (Token, error)
1212
RefreshAccessToken(token Token, tokenPayload map[string]interface{}) (Token, error)
1313
NewInviteToken(email, role, audience string, data map[string]interface{}) (Token, error)
1414
NewResetToken(userID string) (Token, error)

0 commit comments

Comments
 (0)