Skip to content

Commit

Permalink
Merge pull request #419 from MadAppGang/feature/login-scopes-refactoring
Browse files Browse the repository at this point in the history
Feature/login scopes refactoring
  • Loading branch information
hummerdmag authored Aug 26, 2024
2 parents d77fd4f + 1f4a461 commit 104ece4
Show file tree
Hide file tree
Showing 29 changed files with 149 additions and 82 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*.out
vendor
db.db
db_plugin.db
shared-local-instance.db
debug
*__debug_bin
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ build:
go build -o ./identifo

lint:
golangci-lint run -D deadcode,errcheck,unused,varcheck,govet
golangci-lint run -D errcheck,unused,govet

build_admin_panel:
rm -rf static/admin_panel
Expand Down
7 changes: 6 additions & 1 deletion cmd/config-boltdb.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ storage:
type: boltdb
boltdb:
path: ./db.db
userStorage: *storage_settings
userStorage:
type: plugin
plugin:
cmd: ./plugins/bin/bolt-user-storage
params: { "path": "./db_plugin.db" }
redirectStd: true
tokenStorage: *storage_settings
tokenBlacklist: *storage_settings
verificationCodeStorage: *storage_settings
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/sessions v1.2.1
github.com/hashicorp/go-hclog v0.14.1
github.com/hashicorp/go-plugin v1.4.5
github.com/hummerd/httpdump v0.9.1
github.com/joho/godotenv v1.4.0
Expand Down Expand Up @@ -70,7 +71,6 @@ require (
github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/hashicorp/go-hclog v0.14.1 // indirect
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/klauspost/compress v1.13.6 // indirect
Expand Down
6 changes: 5 additions & 1 deletion impersonation/plugin/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import (
"os/exec"
"time"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
grpcShared "github.com/madappgang/identifo/v2/impersonation/grpc/shared"
"github.com/madappgang/identifo/v2/impersonation/plugin/shared"
"github.com/madappgang/identifo/v2/model"
)

func NewImpersonationProvider(settings model.PluginSettings, timeout time.Duration) (model.ImpersonationProvider, error) {
var err error
params := []string{}
for k, v := range settings.Params {
params = append(params, "-"+k)
Expand All @@ -24,6 +24,10 @@ func NewImpersonationProvider(settings model.PluginSettings, timeout time.Durati
Plugins: shared.PluginMap,
Cmd: exec.Command(settings.Cmd, params...),
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
Logger: hclog.New(&hclog.LoggerOptions{
Level: hclog.Debug,
JSONFormat: true,
}),
}

if settings.RedirectStd {
Expand Down
33 changes: 21 additions & 12 deletions jwt/service/jwt_token_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ func (ts *JWTokenService) ValidateTokenString(tstr string, v jwtValidator.Valida
// NewAccessToken creates new access token for user.
func (ts *JWTokenService) NewAccessToken(
user model.User,
scopes []string,
scopes model.AllowedScopesSet,
app model.AppData,
requireTFA bool,
tokenPayload map[string]interface{},
Expand All @@ -235,10 +235,11 @@ func (ts *JWTokenService) NewAccessToken(
payload[PayloadName] = user.Username
}

tokenType := model.TokenTypeAccess
scopesStr := scopes.String()
if requireTFA {
scopes = []string{model.TokenTypeTFAPreauth}
scopesStr = model.TokenTypeTFAPreauth
}

if len(tokenPayload) > 0 {
for k, v := range tokenPayload {
payload[k] = v
Expand All @@ -253,9 +254,9 @@ func (ts *JWTokenService) NewAccessToken(
}

claims := &model.Claims{
Scopes: strings.Join(scopes, " "),
Scopes: scopesStr,
Payload: payload,
Type: tokenType,
Type: model.TokenTypeAccess,
StandardClaims: jwt.StandardClaims{
ExpiresAt: (now + lifespan),
Issuer: ts.issuer,
Expand All @@ -278,22 +279,27 @@ func (ts *JWTokenService) NewAccessToken(
}

// NewRefreshToken creates new refresh token.
func (ts *JWTokenService) NewRefreshToken(u model.User, scopes []string, app model.AppData) (model.Token, error) {
func (ts *JWTokenService) NewRefreshToken(
user model.User,
scopes model.AllowedScopesSet,
app model.AppData,
) (model.Token, error) {
if !app.Active || !app.Offline {
return nil, ErrInvalidApp
}

// no offline request
if !model.SliceContains(scopes, model.OfflineScope) {
if !scopes.Contains(model.OfflineScope) {
return nil, ErrInvalidOfflineScope
}

if !u.Active {
if !user.Active {
return nil, ErrInvalidUser
}

payload := make(map[string]interface{})
if model.SliceContains(app.TokenPayload, PayloadName) {
payload[PayloadName] = u.Username
payload[PayloadName] = user.Username
}
now := ijwt.TimeFunc().Unix()

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

claims := &model.Claims{
Scopes: strings.Join(scopes, " "),
Scopes: scopes.String(),
Payload: payload,
Type: model.TokenTypeRefresh,
StandardClaims: jwt.StandardClaims{
ExpiresAt: (now + lifespan),
Issuer: ts.issuer,
Subject: u.ID,
Subject: user.ID,
Audience: app.ID,
IssuedAt: now,
},
Expand Down Expand Up @@ -338,7 +344,10 @@ func (ts *JWTokenService) NewRefreshToken(u model.User, scopes []string, app mod
}

// RefreshAccessToken issues new access token for provided refresh token.
func (ts *JWTokenService) RefreshAccessToken(refreshToken model.Token, tokenPayload map[string]interface{}) (model.Token, error) {
func (ts *JWTokenService) RefreshAccessToken(
refreshToken model.Token,
tokenPayload map[string]interface{},
) (model.Token, error) {
rt, ok := refreshToken.(*model.JWToken)
if !ok || rt == nil {
return nil, model.ErrTokenInvalid
Expand Down
6 changes: 5 additions & 1 deletion jwt/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func TestNewToken(t *testing.T) {
}, "password", "admin", false)
scopes := []string{"scope1", "scope2"}
tokenPayload := []string{"name"}

app := model.AppData{
ID: "123456",
Secret: "1",
Expand All @@ -138,7 +139,10 @@ func TestNewToken(t *testing.T) {
RolesBlacklist: []string{},
NewUserDefaultRole: "",
}
token, err := tokenService.NewAccessToken(user, scopes, app, false, nil)

allowedScopes := model.AllowedScopes(scopes, scopes, false)

token, err := tokenService.NewAccessToken(user, allowedScopes, app, false, nil)
assert.NoError(t, err)

tokenString, err := tokenService.String(token)
Expand Down
6 changes: 5 additions & 1 deletion model/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ func SliceIntersect(a, b []string) []string {
}

func SliceContains(s []string, e string) bool {
el := strings.TrimSpace(e)

for _, a := range s {
if strings.TrimSpace(strings.ToLower(a)) == strings.TrimSpace(strings.ToLower(e)) {
if strings.EqualFold(strings.TrimSpace(a), el) {
return true
}

}

return false
}

Expand Down
33 changes: 25 additions & 8 deletions model/token.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package model

import (
"strings"
"time"

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

func AllowedScopes(requestedScopes, userScopes []string, isOffline bool) []string {
scopes := []string{}
// This type is needed for guard against passing unchecked scopes to the token.
// Do not convert user provided scopes to this type directly.
type AllowedScopesSet struct {
scopes []string
}

func (a AllowedScopesSet) String() string {
return strings.Join(a.scopes, " ")
}

func (a AllowedScopesSet) Scopes() []string {
return a.scopes
}

func (a AllowedScopesSet) Contains(scope string) bool {
return SliceContains(a.scopes, scope)
}

func AllowedScopes(requestedScopes, userScopes []string, isOffline bool) AllowedScopesSet {
// if we requested any scope, let's provide all the scopes user has and requested
if len(requestedScopes) > 0 {
scopes = SliceIntersect(requestedScopes, userScopes)
}
if SliceContains(requestedScopes, "offline") && isOffline {
scopes = append(scopes, "offline")
scopes := SliceIntersect(requestedScopes, userScopes)

if SliceContains(requestedScopes, OfflineScope) && isOffline {
scopes = append(scopes, OfflineScope)
}

return scopes
return AllowedScopesSet{scopes}
}
4 changes: 2 additions & 2 deletions model/token_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ const (

// TokenService is an abstract token manager.
type TokenService interface {
NewAccessToken(u User, scopes []string, app AppData, requireTFA bool, tokenPayload map[string]interface{}) (Token, error)
NewRefreshToken(u User, scopes []string, app AppData) (Token, error)
NewAccessToken(u User, scopes AllowedScopesSet, app AppData, requireTFA bool, tokenPayload map[string]interface{}) (Token, error)
NewRefreshToken(u User, scopes AllowedScopesSet, app AppData) (Token, error)
RefreshAccessToken(token Token, tokenPayload map[string]interface{}) (Token, error)
NewInviteToken(email, role, audience string, data map[string]interface{}) (Token, error)
NewResetToken(userID string) (Token, error)
Expand Down
15 changes: 14 additions & 1 deletion plugins/bolt-user-storage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"flag"
"log/slog"
"os"
"os/signal"
"syscall"
Expand All @@ -13,7 +14,20 @@ import (
"github.com/madappgang/identifo/v2/storage/plugin/shared"
)

type wproxy struct {
}

func (w wproxy) Write(p []byte) (n int, err error) {
return os.Stderr.Write(p)
}

func main() {
slog.SetDefault(slog.New(slog.NewJSONHandler(
wproxy{},
&slog.HandlerOptions{
Level: slog.LevelDebug,
})))

path := flag.String("path", "", "path to database")
flag.Parse()

Expand All @@ -34,7 +48,6 @@ func main() {
Plugins: map[string]plugin.Plugin{
"user-storage": &shared.UserStoragePlugin{Impl: s},
},

// A non-nil value here enables gRPC serving for this plugin...
GRPCServer: plugin.DefaultGRPCServer,
})
Expand Down
9 changes: 7 additions & 2 deletions storage/dynamodb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,17 @@ func (db *DB) IsTableExists(table string) (bool, error) {
}

func (db *DB) DeleteTable(table string) error {
svc := dynamodb.New(session.New())
sess, err := session.NewSession()
if err != nil {
return err
}

svc := dynamodb.New(sess)
input := &dynamodb.DeleteTableInput{
TableName: aws.String(table),
}

_, err := svc.DeleteTable(input)
_, err = svc.DeleteTable(input)
return err
}

Expand Down
1 change: 1 addition & 0 deletions storage/dynamodb/invite.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ func (is *InviteStorage) ArchiveAllByEmail(email string) error {
if err != nil {
is.logger.Error("Error querying for invites",
logging.FieldError, err)

return ErrorInternalError
}

Expand Down
1 change: 1 addition & 0 deletions storage/dynamodb/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ func (us *UserStorage) UserByID(id string) (model.User, error) {
if err != nil {
us.logger.Error("Error getting item from DynamoDB",
logging.FieldError, err)

return model.User{}, ErrorInternalError
}
if result.Item == nil {
Expand Down
6 changes: 5 additions & 1 deletion storage/plugin/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"
"os/exec"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/madappgang/identifo/v2/model"
grpcShared "github.com/madappgang/identifo/v2/storage/grpc/shared"
Expand All @@ -12,7 +13,6 @@ import (

// NewUserStorage creates and inits plugin user storage.
func NewUserStorage(settings model.PluginSettings) (model.UserStorage, error) {
var err error
params := []string{}
for k, v := range settings.Params {
params = append(params, "-"+k)
Expand All @@ -24,6 +24,10 @@ func NewUserStorage(settings model.PluginSettings) (model.UserStorage, error) {
Plugins: shared.PluginMap,
Cmd: exec.Command(settings.Cmd, params...),
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
Logger: hclog.New(&hclog.LoggerOptions{
Level: hclog.Debug,
JSONFormat: true,
}),
}

if settings.RedirectStd {
Expand Down
7 changes: 5 additions & 2 deletions user_payload_provider/plugin/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os/exec"
"time"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/madappgang/identifo/v2/model"
grpcShared "github.com/madappgang/identifo/v2/user_payload_provider/grpc/shared"
Expand All @@ -13,19 +14,21 @@ import (

// NewTokenPayloadProvider creates and inits plugin for payload provider.
func NewTokenPayloadProvider(settings model.PluginSettings, timeout time.Duration) (model.TokenPayloadProvider, error) {
var err error
params := []string{}
for k, v := range settings.Params {
params = append(params, "-"+k)
params = append(params, v)
}

cfg := &plugin.ClientConfig{
SyncStdout: os.Stdout,
HandshakeConfig: shared.Handshake,
Plugins: shared.PluginMap,
Cmd: exec.Command(settings.Cmd, params...),
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
Logger: hclog.New(&hclog.LoggerOptions{
Level: hclog.Debug,
JSONFormat: true,
}),
}

if settings.RedirectStd {
Expand Down
Loading

0 comments on commit 104ece4

Please sign in to comment.