diff --git a/authorizer/src/asapo_authorizer/cli/cli.go b/authorizer/src/asapo_authorizer/cli/cli.go index 3bfde762cae35679406182a4b8abdc15adf5073b..e39bbd65638b2cfc93c02ec9023870f1d4acd5b9 100644 --- a/authorizer/src/asapo_authorizer/cli/cli.go +++ b/authorizer/src/asapo_authorizer/cli/cli.go @@ -3,8 +3,7 @@ package cli import ( - "asapo_authorizer/database" - "asapo_authorizer/server" + "asapo_authorizer/token_store" "errors" "flag" "fmt" @@ -18,7 +17,7 @@ var flHelp bool var outBuf io.Writer = os.Stdout -var db database.Agent +var store token_store.Store func printHelp(f *flag.FlagSet) bool { if flHelp { @@ -43,10 +42,9 @@ func DoCommand(name string, args []string) error { method := methodVal.Interface().(func() error) - server.CreateDiscoveryService() - db = new(database.Mongodb) - server.InitDB(db) - defer db.Close() + store = new(token_store.TokenStore) + store.Init(nil) + defer store.Close() return method() } diff --git a/authorizer/src/asapo_authorizer/cli/command_test.go b/authorizer/src/asapo_authorizer/cli/command_test.go index dd457455f141710adbc4ffed143ad0bdc8d045a6..6736fc763650de6f3f1f6dd25075af7a93de67b9 100644 --- a/authorizer/src/asapo_authorizer/cli/command_test.go +++ b/authorizer/src/asapo_authorizer/cli/command_test.go @@ -5,30 +5,31 @@ import ( "asapo_authorizer/server" "asapo_common/utils" "bytes" - "testing" "github.com/stretchr/testify/assert" + "testing" ) var CommandTests = []struct { - cmd command - ok bool - msg string + cmd command + error string + msg string }{ - {command{"create-token", []string{"-type", "user-token", "-beamtime","123","-access-types","read","-duration-days","1"}}, true,"ok"}, - {command{"dummy", []string{"description"}}, false,"wrong command"}, + {command{"create-token", []string{"-type", "user-token", "-beamtime", "123", "-access-types", "read", "-duration-days", "1"}}, "database", "ok"}, + {command{"list-tokens", []string{}}, "database", "ok"}, + {command{"revoke-token", []string{"-token","123"}}, "database", "ok"}, + {command{"revoke-token", []string{"-token-id","123"}}, "database", "ok"}, + {command{"dummy", []string{"description"}}, "wrong", "wrong command"}, } func TestCommand(t *testing.T) { outBuf = new(bytes.Buffer) - server.Auth = authorization.NewAuth(utils.NewJWTAuth("secret"),utils.NewJWTAuth("secret_admin"),utils.NewJWTAuth("secret")) + server.Auth = authorization.NewAuth(utils.NewJWTAuth("secret"), utils.NewJWTAuth("secret_admin"), utils.NewJWTAuth("secret")) for _, test := range CommandTests { outBuf.(*bytes.Buffer).Reset() err := DoCommand(test.cmd.name, test.cmd.args) - if !test.ok { - assert.NotNil(t, err, "Should be error",test.msg) - } else { - assert.Nil(t, err, "Should be ok",test.msg) + if err != nil { + assert.Contains(t, err.Error(), test.error) } } diff --git a/authorizer/src/asapo_authorizer/cli/create_token.go b/authorizer/src/asapo_authorizer/cli/create_token.go index 5ddd51c7e2d8253b799c7e4f2c2dd472c0323600..4d267a204f3f2fe4caed814e5e406fac8b33cfe8 100644 --- a/authorizer/src/asapo_authorizer/cli/create_token.go +++ b/authorizer/src/asapo_authorizer/cli/create_token.go @@ -2,13 +2,14 @@ package cli import ( "asapo_authorizer/authorization" - "asapo_authorizer/database" "asapo_authorizer/server" + "asapo_authorizer/token_store" "asapo_common/structs" "errors" "fmt" "os" "strings" + "time" ) type tokenFlags struct { @@ -87,13 +88,10 @@ func (cmd *command) CommandCreate_token() (err error) { if err != nil { return err } - - record := database.TokenRecord{claims.Id, claims, token} - _, err = db.ProcessRequest(database.Request{ - DbName: database.KAdminDb, - Collection: database.KTokens, - Op: "create_record", - }, &record) + claims.StandardClaims.Issuer = "asapo_cli" + claims.StandardClaims.IssuedAt = time.Now().Unix() + record := token_store.TokenRecord{claims.Id, claims, token, false} + err = store.AddToken(record) if err != nil { return err } diff --git a/authorizer/src/asapo_authorizer/cli/create_token_test.go b/authorizer/src/asapo_authorizer/cli/create_token_test.go index a3e33caad6d76ed455dbab2ccd668f0d880a306a..bedd7476bfdbcecfcc1dda25ad6436bc46a7eb44 100644 --- a/authorizer/src/asapo_authorizer/cli/create_token_test.go +++ b/authorizer/src/asapo_authorizer/cli/create_token_test.go @@ -2,8 +2,8 @@ package cli import ( "asapo_authorizer/authorization" - "asapo_authorizer/database" "asapo_authorizer/server" + "asapo_authorizer/token_store" "asapo_common/structs" "asapo_common/utils" "encoding/json" @@ -43,20 +43,14 @@ var tokenTests = []struct { func TestGenerateToken(t *testing.T) { server.Auth = authorization.NewAuth(utils.NewJWTAuth("secret_user"),utils.NewJWTAuth("secret_admin"),utils.NewJWTAuth("secret")) - mock_db := new(database.MockedDatabase) - db = mock_db + mock_store := new(token_store.MockedStore) + store = mock_store for _, test := range tokenTests { outBuf = new(bytes.Buffer) if test.ok { - req := database.Request{ - DbName: "asapo_admin", - Collection: "tokens", - Op: "create_record", - } - - mock_db.On("ProcessRequest", req, mock.Anything).Return([]byte(""), nil) + mock_store.On("AddToken", mock.Anything).Return(nil) } err := test.cmd.CommandCreate_token() @@ -81,8 +75,8 @@ func TestGenerateToken(t *testing.T) { assert.Empty(t, token.Expires, test.msg) } - mock_db.AssertExpectations(t) - mock_db.ExpectedCalls = nil - mock_db.Calls = nil + mock_store.AssertExpectations(t) + mock_store.ExpectedCalls = nil + mock_store.Calls = nil } } diff --git a/authorizer/src/asapo_authorizer/cli/list_tokens.go b/authorizer/src/asapo_authorizer/cli/list_tokens.go index 0c8397ebac41ef930795c95ba2052e8822a7f26d..923cc8f8b622b38023b1c256f190bce8faf478c9 100644 --- a/authorizer/src/asapo_authorizer/cli/list_tokens.go +++ b/authorizer/src/asapo_authorizer/cli/list_tokens.go @@ -1,55 +1,23 @@ package cli import ( - "asapo_authorizer/authorization" - "asapo_authorizer/database" - "asapo_authorizer/server" + "encoding/json" "fmt" - "os" ) -type listTokenFlags struct { - Beamtime string - Beamline string -} - func (cmd *command) CommandList_tokens() (err error) { message_string := "List tokens" if cmd.description(message_string) { return nil } - _, err = cmd.parseTokenFlags(message_string) - if err != nil { - return err - } - - var res map[string]interface{} - _, err = db.ProcessRequest(database.Request{ - DbName: database.KAdminDb, - Collection: database.KTokens, - Op: "list_records", - }, &res) + tokens,err := store.GetTokenList() if err != nil { return err } - answer := authorization.UserTokenResponce(request, token) + answer,_ := json.Marshal(tokens) fmt.Fprintf(outBuf, "%s\n", string(answer)) return nil } -func (cmd *command) parseListTokenFlags(message_string string) (tokenFlags, error) { - - var flags tokenFlags - flagset := cmd.createDefaultFlagset(message_string, "") - flagset.StringVar(&flags.Beamtime, "beamtime", "", "beamtime for user token") - flagset.StringVar(&flags.Beamline, "beamline", "", "beamline for user token") - - flagset.Parse(cmd.args) - - if printHelp(flagset) { - os.Exit(0) - } - return flags, nil -} diff --git a/authorizer/src/asapo_authorizer/cli/list_tokens_test.go b/authorizer/src/asapo_authorizer/cli/list_tokens_test.go new file mode 100644 index 0000000000000000000000000000000000000000..dd4b74db6ad9a4ea70c38673a6fa89e7691ae4cb --- /dev/null +++ b/authorizer/src/asapo_authorizer/cli/list_tokens_test.go @@ -0,0 +1,18 @@ +package cli + +import ( + "asapo_authorizer/token_store" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +func TestListTokens(t *testing.T) { + mock_store := new(token_store.MockedStore) + store = mock_store + + mock_store.On("GetTokenList", mock.Anything).Return([]token_store.TokenRecord{}, nil) + c := command{"list-tokens", []string{}} + err := c.CommandList_tokens() + assert.Nil(t, err) +} \ No newline at end of file diff --git a/authorizer/src/asapo_authorizer/cli/revoke_token.go b/authorizer/src/asapo_authorizer/cli/revoke_token.go new file mode 100644 index 0000000000000000000000000000000000000000..83712600298c8d423b34b790bf1e0ab023b7f02c --- /dev/null +++ b/authorizer/src/asapo_authorizer/cli/revoke_token.go @@ -0,0 +1,57 @@ +package cli + +import ( + "encoding/json" + "errors" + "fmt" + "os" +) + +type revokeTokenFlags struct { + Token string + TokenId string +} + +func (cmd *command) CommandRevoke_token() (err error) { + message_string := "Revoke token" + if cmd.description(message_string) { + return nil + } + + flags, err := cmd.parseRevokeTokenFlags(message_string) + if err != nil { + return err + } + token,err := store.RevokeToken(flags.Token, flags.TokenId) + if err != nil { + return err + } + + out, _ := json.Marshal(token) + fmt.Fprintln(outBuf, string(out)) + return nil +} + +func (cmd *command) parseRevokeTokenFlags(message_string string) (revokeTokenFlags, error) { + + var flags revokeTokenFlags + flagset := cmd.createDefaultFlagset(message_string, "") + flagset.StringVar(&flags.Token, "token", "", "token to revoke") + flagset.StringVar(&flags.TokenId, "token-id", "", "token id to revoke") + flagset.Parse(cmd.args) + + if printHelp(flagset) { + os.Exit(0) + } + + if flags.Token == "" && flags.TokenId == "" { + return flags, errors.New("set token or token id to revoke") + } + + if flags.Token != "" && flags.TokenId != "" { + return flags, errors.New("cannot use both token and token id") + } + + return flags, nil + +} diff --git a/authorizer/src/asapo_authorizer/cli/revoke_token_test.go b/authorizer/src/asapo_authorizer/cli/revoke_token_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8be7a957b7118e5ac10816d65e12b124597790ed --- /dev/null +++ b/authorizer/src/asapo_authorizer/cli/revoke_token_test.go @@ -0,0 +1,27 @@ +package cli + +import ( + "asapo_authorizer/token_store" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestRevokeTokenToken(t *testing.T) { + mock_store := new(token_store.MockedStore) + store = mock_store + + mock_store.On("RevokeToken", "123","").Return(token_store.TokenRecord{}, nil) + c := command{"revoke-token", []string{"-token","123"}} + err := c.CommandRevoke_token() + assert.Nil(t, err) +} + +func TestRevokeTokenTokenId(t *testing.T) { + mock_store := new(token_store.MockedStore) + store = mock_store + + mock_store.On("RevokeToken", "","123").Return(token_store.TokenRecord{}, nil) + c := command{"revoke-token", []string{"-token-id","123"}} + err := c.CommandRevoke_token() + assert.Nil(t, err) +} \ No newline at end of file diff --git a/authorizer/src/asapo_authorizer/common/settings.go b/authorizer/src/asapo_authorizer/common/settings.go new file mode 100644 index 0000000000000000000000000000000000000000..48dc90fedccb98c087a36bb5ab61e62f9c68d7ba --- /dev/null +++ b/authorizer/src/asapo_authorizer/common/settings.go @@ -0,0 +1,23 @@ +package common + +var Settings authorizerSettings + +type authorizerSettings struct { + Port int + LogLevel string + RootBeamtimesFolder string + CurrentBeamlinesFolder string + AlwaysAllowedBeamtimes []BeamtimeMeta + UserSecretFile string + AdminSecretFile string + FolderTokenDurationMin int + Ldap struct { + Uri string + BaseDn string + FilterTemplate string + } + DiscoveryServer string + DatabaseServer string + UpdateRevokedTokensIntervalSec int + UpdateTokenCacheIntervalSec int +} diff --git a/authorizer/src/asapo_authorizer/common/structs.go b/authorizer/src/asapo_authorizer/common/structs.go index 805d0c79aadd885ddb6ed76b1e678c10a63bbe45..ca2a6de00c0f7c82371580343293eae2a097ca80 100644 --- a/authorizer/src/asapo_authorizer/common/structs.go +++ b/authorizer/src/asapo_authorizer/common/structs.go @@ -1 +1,17 @@ package common + +type BeamtimeMeta struct { + BeamtimeId string `json:"beamtimeId"` + Beamline string `json:"beamline"` + DataSource string `json:"dataSource"` + OfflinePath string `json:"corePath"` + OnlinePath string `json:"beamline-path"` + Type string `json:"source-type"` + AccessTypes []string `json:"access-types"` +} + +type CommissioningMeta struct { + Id string `json:"id"` + Beamline string `json:"beamline"` + OfflinePath string `json:"corePath"` +} diff --git a/authorizer/src/asapo_authorizer/server/authorize.go b/authorizer/src/asapo_authorizer/server/authorize.go index aaaade1b34aca6afa722a4798afd785ef68a4f38..e8faa9caaef9e2fa14a35f58561579e23fe3157d 100644 --- a/authorizer/src/asapo_authorizer/server/authorize.go +++ b/authorizer/src/asapo_authorizer/server/authorize.go @@ -60,21 +60,21 @@ func splitHost(hostPort string) string { return s[0] } -func beamtimeMetaFromJson(fname string) (beamtimeMeta, error) { - var meta beamtimeMeta +func beamtimeMetaFromJson(fname string) (common.BeamtimeMeta, error) { + var meta common.BeamtimeMeta err := utils.ReadJsonFromFile(fname, &meta) if err != nil { - return beamtimeMeta{}, err + return common.BeamtimeMeta{}, err } return meta, nil } -func commissioningMetaFromJson(fname string) (beamtimeMeta, error) { - var meta beamtimeMeta - var comMeta commissioningMeta +func commissioningMetaFromJson(fname string) (common.BeamtimeMeta, error) { + var meta common.BeamtimeMeta + var comMeta common.CommissioningMeta err := utils.ReadJsonFromFile(fname, &comMeta) if err != nil { - return beamtimeMeta{}, err + return common.BeamtimeMeta{}, err } meta.BeamtimeId = comMeta.Id meta.Beamline = strings.ToLower(comMeta.Beamline) @@ -82,32 +82,32 @@ func commissioningMetaFromJson(fname string) (beamtimeMeta, error) { return meta, nil } -func beamtimeMetaFromMatch(match string) (beamtimeMeta, error) { - match = strings.TrimPrefix(match, settings.RootBeamtimesFolder) +func beamtimeMetaFromMatch(match string) (common.BeamtimeMeta, error) { + match = strings.TrimPrefix(match, common.Settings.RootBeamtimesFolder) match = strings.TrimPrefix(match, string(filepath.Separator)) vars := strings.Split(match, string(filepath.Separator)) if len(vars) != 6 { - return beamtimeMeta{}, errors.New("bad pattern") + return common.BeamtimeMeta{}, errors.New("bad pattern") } - var bt beamtimeMeta + var bt common.BeamtimeMeta ignoredFoldersAfterGpfs := []string{"common", "BeamtimeUsers", "state", "support"} if utils.StringInSlice(vars[2], ignoredFoldersAfterGpfs) { - return beamtimeMeta{}, errors.New("skipped fodler") + return common.BeamtimeMeta{}, errors.New("skipped fodler") } - bt.OfflinePath = settings.RootBeamtimesFolder+string(filepath.Separator)+match + bt.OfflinePath = common.Settings.RootBeamtimesFolder+string(filepath.Separator)+match bt.Beamline, bt.BeamtimeId = vars[2], vars[5] return bt, nil } -func findBeamtimeInfoFromId(beamtime_id string) (beamtimeMeta, error) { +func findBeamtimeInfoFromId(beamtime_id string) (common.BeamtimeMeta, error) { sep := string(filepath.Separator) pattern := sep + "*" + sep + "gpfs" + sep + "*" + sep + "*" + sep + "*" + sep - matches, err := filepath.Glob(settings.RootBeamtimesFolder + pattern + beamtime_id) + matches, err := filepath.Glob(common.Settings.RootBeamtimesFolder + pattern + beamtime_id) if err != nil || len(matches) == 0 { - return beamtimeMeta{}, errors.New("Cannot find beamline for "+beamtime_id) + return common.BeamtimeMeta{}, errors.New("Cannot find beamline for "+beamtime_id) } for _, match := range (matches) { @@ -119,7 +119,7 @@ func findBeamtimeInfoFromId(beamtime_id string) (beamtimeMeta, error) { return btInfo, nil } } - return beamtimeMeta{}, errors.New("Cannot find beamline for "+beamtime_id) + return common.BeamtimeMeta{}, errors.New("Cannot find beamline for "+beamtime_id) } func findMetaFileInFolder(beamline string,iscommissioning bool) (string, string, error){ @@ -132,7 +132,7 @@ func findMetaFileInFolder(beamline string,iscommissioning bool) (string, string, pattern = "commissioning-metadata-*.json" folder = "commissioning" } - online_path := settings.CurrentBeamlinesFolder + sep + beamline + sep + folder + online_path := common.Settings.CurrentBeamlinesFolder + sep + beamline + sep + folder matches, err := filepath.Glob(online_path + sep + pattern) if err != nil { return "","", err @@ -144,10 +144,10 @@ func findMetaFileInFolder(beamline string,iscommissioning bool) (string, string, } -func findBeamtimeMetaFromBeamline(beamline string,iscommissioning bool) (meta beamtimeMeta, err error) { +func findBeamtimeMetaFromBeamline(beamline string,iscommissioning bool) (meta common.BeamtimeMeta, err error) { fName,online_path, err := findMetaFileInFolder(beamline,iscommissioning) if (err != nil) { - return beamtimeMeta{}, err + return common.BeamtimeMeta{}, err } if iscommissioning { @@ -156,19 +156,19 @@ func findBeamtimeMetaFromBeamline(beamline string,iscommissioning bool) (meta be meta, err = beamtimeMetaFromJson(fName) } if (err != nil) { - return beamtimeMeta{}, err + return common.BeamtimeMeta{}, err } if meta.BeamtimeId == "" || meta.OfflinePath=="" || meta.Beamline == ""{ - return beamtimeMeta{}, errors.New("cannot set meta fields from beamtime file") + return common.BeamtimeMeta{}, errors.New("cannot set meta fields from beamtime file") } meta.OnlinePath = online_path return meta, nil } -func alwaysAllowed(creds SourceCredentials) (beamtimeMeta, bool) { - for _, pair := range settings.AlwaysAllowedBeamtimes { +func alwaysAllowed(creds SourceCredentials) (common.BeamtimeMeta, bool) { + for _, pair := range common.Settings.AlwaysAllowedBeamtimes { if pair.BeamtimeId == creds.BeamtimeId { pair.DataSource = creds.DataSource pair.Type = creds.Type @@ -176,12 +176,12 @@ func alwaysAllowed(creds SourceCredentials) (beamtimeMeta, bool) { return pair, true } } - return beamtimeMeta{}, false + return common.BeamtimeMeta{}, false } func authorizeByHost(host_ip, beamline string) (error) { - filter := strings.Replace(settings.Ldap.FilterTemplate,"__BEAMLINE__",beamline,1) - allowed_ips, err := ldapClient.GetAllowedIpsForBeamline(settings.Ldap.Uri,settings.Ldap.BaseDn, filter) + filter := strings.Replace(common.Settings.Ldap.FilterTemplate,"__BEAMLINE__",beamline,1) + allowed_ips, err := ldapClient.GetAllowedIpsForBeamline(common.Settings.Ldap.Uri,common.Settings.Ldap.BaseDn, filter) if err != nil { log.Error("cannot get list of allowed hosts from LDAP: " + err.Error()) return err @@ -230,9 +230,9 @@ func iscommissioning(beamtime string) bool { return len(beamtime)>0 && beamtime[0]=='c' } -func findMeta(creds SourceCredentials) (beamtimeMeta, error) { +func findMeta(creds SourceCredentials) (common.BeamtimeMeta, error) { var err error - var meta beamtimeMeta + var meta common.BeamtimeMeta if (creds.BeamtimeId != "auto") { meta, err = findBeamtimeInfoFromId(creds.BeamtimeId) if (err == nil ) { @@ -251,7 +251,7 @@ func findMeta(creds SourceCredentials) (beamtimeMeta, error) { if (err != nil) { log.Error(err.Error()) - return beamtimeMeta{}, err + return common.BeamtimeMeta{}, err } meta.DataSource = creds.DataSource @@ -260,7 +260,7 @@ func findMeta(creds SourceCredentials) (beamtimeMeta, error) { return meta, nil } -func authorizeMeta(meta beamtimeMeta, request authorizationRequest, creds SourceCredentials) (accessTypes []string, err error) { +func authorizeMeta(meta common.BeamtimeMeta, request authorizationRequest, creds SourceCredentials) (accessTypes []string, err error) { accessTypes = nil if creds.Type=="raw" && meta.OnlinePath=="" { err_string := "beamtime "+meta.BeamtimeId+" is not online" @@ -290,19 +290,19 @@ func authorizeMeta(meta beamtimeMeta, request authorizationRequest, creds Source return accessTypes,err } -func authorize(request authorizationRequest, creds SourceCredentials) (beamtimeMeta, error) { +func authorize(request authorizationRequest, creds SourceCredentials) (common.BeamtimeMeta, error) { if meta, ok := alwaysAllowed(creds); ok { return meta, nil } meta, err := findMeta(creds) if err != nil { - return beamtimeMeta{}, err + return common.BeamtimeMeta{}, err } var accessTypes []string if accessTypes, err = authorizeMeta(meta, request, creds); err != nil { - return beamtimeMeta{}, err + return common.BeamtimeMeta{}, err } meta.AccessTypes = accessTypes diff --git a/authorizer/src/asapo_authorizer/server/authorize_test.go b/authorizer/src/asapo_authorizer/server/authorize_test.go index 3c5f2838824cf44f5dca55009645f4f9e06cab9b..7272ea101c0c1fed437ca450580779fbd016333d 100644 --- a/authorizer/src/asapo_authorizer/server/authorize_test.go +++ b/authorizer/src/asapo_authorizer/server/authorize_test.go @@ -50,8 +50,8 @@ type request struct { message string } -func allowBeamlines(beamlines []beamtimeMeta) { - settings.AlwaysAllowedBeamtimes=beamlines +func allowBeamlines(beamlines []common.BeamtimeMeta) { + common.Settings.AlwaysAllowedBeamtimes=beamlines } @@ -111,7 +111,7 @@ func TestSplitCreds(t *testing.T) { } func TestAuthorizeDefaultOK(t *testing.T) { - allowBeamlines([]beamtimeMeta{{"asapo_test","beamline","","2019","tf","",nil}}) + allowBeamlines([]common.BeamtimeMeta{{"asapo_test","beamline","","2019","tf","",nil}}) request := makeRequest(authorizationRequest{"processed%asapo_test%%%","host"}) w := doPostRequest("/authorize",request,"") @@ -244,16 +244,16 @@ var authTests = [] struct { func TestAuthorize(t *testing.T) { ldapClient = mockClient - allowBeamlines([]beamtimeMeta{}) + allowBeamlines([]common.BeamtimeMeta{}) Auth = authorization.NewAuth(utils.NewJWTAuth("secret_user"),utils.NewJWTAuth("secret_admin"),utils.NewJWTAuth("secret")) expected_uri := "expected_uri" expected_base := "expected_base" allowed_ips := []string{"127.0.0.1"} - settings.RootBeamtimesFolder ="." - settings.CurrentBeamlinesFolder="." - settings.Ldap.FilterTemplate="a3__BEAMLINE__-hosts" - settings.Ldap.Uri = expected_uri - settings.Ldap.BaseDn = expected_base + common.Settings.RootBeamtimesFolder ="." + common.Settings.CurrentBeamlinesFolder="." + common.Settings.Ldap.FilterTemplate="a3__BEAMLINE__-hosts" + common.Settings.Ldap.Uri = expected_uri + common.Settings.Ldap.BaseDn = expected_base os.MkdirAll(filepath.Clean("tf/gpfs/bl1/2019/data/test"), os.ModePerm) os.MkdirAll(filepath.Clean("tf/gpfs/bl1/2019/data/test_online"), os.ModePerm) @@ -323,7 +323,7 @@ func TestAuthorizeWrongPath(t *testing.T) { } func TestDoNotAuthorizeIfNotInAllowed(t *testing.T) { - allowBeamlines([]beamtimeMeta{{"test","beamline","","2019","tf","",nil}}) + allowBeamlines([]common.BeamtimeMeta{{"test","beamline","","2019","tf","",nil}}) request := authorizationRequest{"asapo_test%%","host"} creds,_ := getSourceCredentials(request) @@ -360,7 +360,7 @@ var extractBtinfoTests = [] struct { } func TestGetBeamtimeInfo(t *testing.T) { for _, test := range extractBtinfoTests { - settings.RootBeamtimesFolder=test.root + common.Settings.RootBeamtimesFolder=test.root bt,err:= beamtimeMetaFromMatch(test.root+string(filepath.Separator)+test.fname) if test.ok { assert.Equal(t,bt.OfflinePath,test.root+string(filepath.Separator)+test.fname) diff --git a/authorizer/src/asapo_authorizer/server/db_request.go b/authorizer/src/asapo_authorizer/server/db_request.go deleted file mode 100644 index 135aca5a24e4108a07294e863f77cde42b7a34d3..0000000000000000000000000000000000000000 --- a/authorizer/src/asapo_authorizer/server/db_request.go +++ /dev/null @@ -1,29 +0,0 @@ -package server - -import ( - "asapo_authorizer/database" - log "asapo_common/logger" - "asapo_common/utils" -) - -func reconnectIfNeeded(db_error error) { - code := database.GetStatusCodeFromError(db_error) - if code != utils.StatusServiceUnavailable { - return - } - - if err := ReconnectDb(); err != nil { - log.Error("cannot reconnect to database at : " + settings.GetDatabaseServer() + " " + err.Error()) - } else { - log.Debug("reconnected to database" + settings.GetDatabaseServer()) - } -} - -func ProcessRequestInDb(request database.Request) ([]byte,error) { - answer, err := db.ProcessRequest(request) - if err != nil { - go reconnectIfNeeded(err) - return nil,err - } - return answer,nil -} diff --git a/authorizer/src/asapo_authorizer/server/db_request_test.go b/authorizer/src/asapo_authorizer/server/db_request_test.go deleted file mode 100644 index 4cd03bfb7c7cc537de725b4d85569d6018fdfae5..0000000000000000000000000000000000000000 --- a/authorizer/src/asapo_authorizer/server/db_request_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package server - -import ( - "asapo_authorizer/database" - "asapo_common/logger" - "asapo_common/utils" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/suite" - "testing" - "time" -) - - -const expectedSource = "datasource" -const expectedStream = "stream" - - -func ExpectReconnect(mock_db *database.MockedDatabase) { - mock_db.On("Close").Return() - mock_db.On("Connect", mock.AnythingOfType("string")).Return(nil) -} - -type ProcessRequestTestSuite struct { - suite.Suite - mock_db *database.MockedDatabase -} - -func (suite *ProcessRequestTestSuite) SetupTest() { - suite.mock_db = new(database.MockedDatabase) - db = suite.mock_db - logger.SetMockLog() -} - -func (suite *ProcessRequestTestSuite) TearDownTest() { - assertExpectations(suite.T(), suite.mock_db) - logger.UnsetMockLog() - db = nil -} - -func TestProcessRequestTestSuite(t *testing.T) { - suite.Run(t, new(ProcessRequestTestSuite)) -} - - -func (suite *ProcessRequestTestSuite) TestProcessRequestWithConnectionError() { - req := database.Request{} - - suite.mock_db.On("ProcessRequest", req,mock.Anything).Return([]byte(""), - &database.DBError{utils.StatusServiceUnavailable, ""}) - - ExpectReconnect(suite.mock_db) - logger.MockLog.On("Debug", mock.MatchedBy(containsMatcher("reconnected"))) - - _,err := ProcessRequestInDb(req) - time.Sleep(time.Second) - suite.Error(err, "need reconnect") -} - - -func (suite *ProcessRequestTestSuite) TestProcessRequestAddTokenToDb() { - req := database.Request{ - DbName: "test", - Collection: "test", - Op: "test", - } - - suite.mock_db.On("ProcessRequest", req,mock.Anything).Return([]byte("Hello"), nil) - - - _,err := ProcessRequestInDb(req) - suite.Equal(err, nil, "ok") - -} diff --git a/authorizer/src/asapo_authorizer/server/folder_token.go b/authorizer/src/asapo_authorizer/server/folder_token.go index dcdfbe38ba74631c5c31ad96dacef18a474dd6ca..5d10ed86a844902a0c33101d0fd7862f5628e854 100644 --- a/authorizer/src/asapo_authorizer/server/folder_token.go +++ b/authorizer/src/asapo_authorizer/server/folder_token.go @@ -1,14 +1,15 @@ package server import ( + "asapo_authorizer/common" + log "asapo_common/logger" "asapo_common/structs" "asapo_common/utils" "asapo_common/version" - "net/http" - "time" - log "asapo_common/logger" "errors" + "net/http" "path/filepath" + "time" ) type folderTokenRequest struct { @@ -27,7 +28,7 @@ type folderToken struct { } /*func routeFolderToken(w http.ResponseWriter, r *http.Request) { - utils.ProcessJWTAuth(processFolderTokenRequest,settings.secret)(w,r) + utils.ProcessJWTAuth(processFolderTokenRequest,common.Settings.secret)(w,r) }*/ func prepareJWTToken(folders tokenFolders) (string, error) { @@ -38,7 +39,7 @@ func prepareJWTToken(folders tokenFolders) (string, error) { extraClaim.SecondFolder = folders.SecondFolder claims.ExtraClaims = &extraClaim - claims.SetExpiration(time.Duration(settings.FolderTokenDurationMin) * time.Minute) + claims.SetExpiration(time.Duration(common.Settings.FolderTokenDurationMin) * time.Minute) return Auth.JWTAuth().GenerateToken(&claims) } diff --git a/authorizer/src/asapo_authorizer/server/folder_token_test.go b/authorizer/src/asapo_authorizer/server/folder_token_test.go index f90bb77eb7c4ee3c5edc1771192a7e628e22f452..88c02cba5f960ab017e1508157ff30ce3d6b355f 100644 --- a/authorizer/src/asapo_authorizer/server/folder_token_test.go +++ b/authorizer/src/asapo_authorizer/server/folder_token_test.go @@ -2,16 +2,17 @@ package server import ( "asapo_authorizer/authorization" + "asapo_authorizer/common" "asapo_common/structs" "asapo_common/utils" "asapo_common/version" + "fmt" "github.com/stretchr/testify/assert" "io/ioutil" "net/http" - "testing" "os" "path/filepath" - "fmt" + "testing" ) var fodlerTokenTests = [] struct { @@ -37,9 +38,9 @@ var fodlerTokenTests = [] struct { } func TestFolderToken(t *testing.T) { - allowBeamlines([]beamtimeMeta{}) - settings.RootBeamtimesFolder ="." - settings.CurrentBeamlinesFolder="." + allowBeamlines([]common.BeamtimeMeta{}) + common.Settings.RootBeamtimesFolder ="." + common.Settings.CurrentBeamlinesFolder="." Auth = authorization.NewAuth(utils.NewJWTAuth("secret_user"),utils.NewJWTAuth("secret_admin"),utils.NewJWTAuth("secret_folder")) os.MkdirAll(filepath.Clean("tf/gpfs/bl1/2019/data/test"), os.ModePerm) @@ -52,10 +53,10 @@ func TestFolderToken(t *testing.T) { defer os.RemoveAll("bl1") for _, test := range fodlerTokenTests { - abs_path:=settings.RootBeamtimesFolder + string(filepath.Separator)+test.root_folder + abs_path:=common.Settings.RootBeamtimesFolder + string(filepath.Separator)+test.root_folder abs_path_second :="" if test.second_folder!="" { - abs_path_second =settings.RootBeamtimesFolder + string(filepath.Separator)+test.second_folder + abs_path_second =common.Settings.RootBeamtimesFolder + string(filepath.Separator)+test.second_folder } path_in_token:=abs_path if test.auto { diff --git a/authorizer/src/asapo_authorizer/server/server.go b/authorizer/src/asapo_authorizer/server/server.go index 8fec39728a532987473239776c7ce6ec6db9e00a..ccb57bab4facbdfde0e6589fe94f9bdbe34f82d2 100644 --- a/authorizer/src/asapo_authorizer/server/server.go +++ b/authorizer/src/asapo_authorizer/server/server.go @@ -2,89 +2,10 @@ package server import ( "asapo_authorizer/authorization" - "asapo_authorizer/database" "asapo_authorizer/ldap_client" - "asapo_common/discovery" - log "asapo_common/logger" - "errors" + "asapo_authorizer/token_store" ) -type beamtimeMeta struct { - BeamtimeId string `json:"beamtimeId"` - Beamline string `json:"beamline"` - DataSource string `json:"dataSource"` - OfflinePath string `json:"corePath"` - OnlinePath string `json:"beamline-path"` - Type string `json:"source-type"` - AccessTypes []string `json:"access-types"` -} - -type commissioningMeta struct { - Id string `json:"id"` - Beamline string `json:"beamline"` - OfflinePath string `json:"corePath"` -} - - -type serverSettings struct { - Port int - LogLevel string - RootBeamtimesFolder string - CurrentBeamlinesFolder string - AlwaysAllowedBeamtimes []beamtimeMeta - UserSecretFile string - AdminSecretFile string - FolderTokenDurationMin int - Ldap struct { - Uri string - BaseDn string - FilterTemplate string - } - DiscoveryServer string - DatabaseServer string - discoveredDbAddress string -} - -func (s *serverSettings) GetDatabaseServer() string { - if s.DatabaseServer == "auto" { - return s.discoveredDbAddress - } else { - return s.DatabaseServer - } -} - -var settings serverSettings var ldapClient ldap_client.LdapClient var Auth *authorization.Auth -var discoveryService discovery.DiscoveryAPI -var db database.Agent - -func ReconnectDb() (err error) { - if db == nil { - return errors.New("database not initialized") - } - db.Close() - return InitDB(db) -} - -func InitDB(dbAgent database.Agent) (err error) { - db = dbAgent - if settings.DatabaseServer == "auto" { - settings.discoveredDbAddress, err = discoveryService.GetMongoDbAddress() - if err != nil { - return err - } - if settings.discoveredDbAddress == "" { - return errors.New("no database servers found") - } - log.Debug("Got mongodb server: " + settings.discoveredDbAddress) - } - - return db.Connect(settings.GetDatabaseServer()) -} - -func CleanupDB() { - if db != nil { - db.Close() - } -} +var store token_store.Store diff --git a/authorizer/src/asapo_authorizer/server/server_nottested.go b/authorizer/src/asapo_authorizer/server/server_nottested.go index a4dec96733a06581603bd1d0caa3dbc13997ffe9..4661af0a6db1f502c854f491b51a183050cd8c9a 100644 --- a/authorizer/src/asapo_authorizer/server/server_nottested.go +++ b/authorizer/src/asapo_authorizer/server/server_nottested.go @@ -4,9 +4,9 @@ package server import ( "asapo_authorizer/authorization" - "asapo_authorizer/database" + "asapo_authorizer/common" "asapo_authorizer/ldap_client" - "asapo_common/discovery" + "asapo_authorizer/token_store" log "asapo_common/logger" "asapo_common/utils" "asapo_common/version" @@ -15,32 +15,29 @@ import ( "strconv" ) -func CreateDiscoveryService() { - discoveryService = discovery.CreateDiscoveryService(&http.Client{},"http://" + settings.DiscoveryServer) -} - func Start() { mux := utils.NewRouter(listRoutes) ldapClient = new(ldap_client.OpenLdapClient) log.Info("Starting ASAPO Authorizer, version " + version.GetVersion()) - CreateDiscoveryService() - err := InitDB(new(database.Mongodb)) + + store = new(token_store.TokenStore) + err := store.Init(nil) if err != nil { log.Error(err.Error()) } - defer CleanupDB() + defer store.Close() - log.Info("Listening on port: " + strconv.Itoa(settings.Port)) - log.Fatal(http.ListenAndServe(":"+strconv.Itoa(settings.Port), http.HandlerFunc(mux.ServeHTTP))) + log.Info("Listening on port: " + strconv.Itoa(common.Settings.Port)) + log.Fatal(http.ListenAndServe(":"+strconv.Itoa(common.Settings.Port), http.HandlerFunc(mux.ServeHTTP))) } func createAuth() (*authorization.Auth,error) { - secret, err := utils.ReadFirstStringFromFile(settings.UserSecretFile) + secret, err := utils.ReadFirstStringFromFile(common.Settings.UserSecretFile) if err != nil { return nil, err } - adminSecret, err := utils.ReadFirstStringFromFile(settings.AdminSecretFile) + adminSecret, err := utils.ReadFirstStringFromFile(common.Settings.AdminSecretFile) if err != nil { return nil, err } @@ -48,27 +45,27 @@ func createAuth() (*authorization.Auth,error) { } func ReadConfig(fname string) (log.Level, error) { - if err := utils.ReadJsonFromFile(fname, &settings); err != nil { + if err := utils.ReadJsonFromFile(fname, &common.Settings); err != nil { return log.FatalLevel, err } - if settings.Port == 0 { + if common.Settings.Port == 0 { return log.FatalLevel, errors.New("Server port not set") } - if settings.UserSecretFile == "" { + if common.Settings.UserSecretFile == "" { return log.FatalLevel, errors.New("User secret file not set") } - if settings.AdminSecretFile == "" { + if common.Settings.AdminSecretFile == "" { return log.FatalLevel, errors.New("Admin secret file not set") } - if settings.DatabaseServer == "" { + if common.Settings.DatabaseServer == "" { return log.FatalLevel, errors.New("Database server not set") } - if settings.DatabaseServer == "auto" && settings.DiscoveryServer=="" { + if common.Settings.DatabaseServer == "auto" && common.Settings.DiscoveryServer=="" { return log.FatalLevel, errors.New("Discovery server not set") } @@ -78,7 +75,7 @@ func ReadConfig(fname string) (log.Level, error) { return log.FatalLevel, err } - level, err := log.LevelFromString(settings.LogLevel) + level, err := log.LevelFromString(common.Settings.LogLevel) return level, err } diff --git a/authorizer/src/asapo_authorizer/server/server_test.go b/authorizer/src/asapo_authorizer/server/server_test.go index 46f045ddfc12553ad377e8e7fd3525c147ff99a1..f18a0f738e4b4ed9397f8449a679f8650b7f6928 100644 --- a/authorizer/src/asapo_authorizer/server/server_test.go +++ b/authorizer/src/asapo_authorizer/server/server_test.go @@ -1,5 +1,7 @@ package server +/* + import ( "asapo_authorizer/database" "asapo_common/discovery" @@ -138,3 +140,4 @@ func TestCleanupDBInit(t *testing.T) { assertExpectations(t, mock_db) } +*/ \ No newline at end of file diff --git a/authorizer/src/asapo_authorizer/database/database.go b/authorizer/src/asapo_authorizer/token_store/database.go similarity index 84% rename from authorizer/src/asapo_authorizer/database/database.go rename to authorizer/src/asapo_authorizer/token_store/database.go index 187adeaa8d055ca9f40ff6fa418aa271f9801c33..2afabbe9ffe0fc058e3da879bb751d466d268042 100644 --- a/authorizer/src/asapo_authorizer/database/database.go +++ b/authorizer/src/asapo_authorizer/token_store/database.go @@ -1,10 +1,11 @@ -package database +package token_store import "asapo_common/utils" const KAdminDb = "asapo_admin" const KTokens = "tokens" +const KRevokedTokens = "revoked_tokens" type Request struct { DbName string @@ -16,6 +17,11 @@ type TokenRecord struct { Id string `bson:"_id"` *utils.CustomClaims Token string + Revoked bool +} + +type IdRecord struct { + Id string `bson:"_id"` } type Agent interface { diff --git a/authorizer/src/asapo_authorizer/database/mock_database.go b/authorizer/src/asapo_authorizer/token_store/mock_database.go similarity index 61% rename from authorizer/src/asapo_authorizer/database/mock_database.go rename to authorizer/src/asapo_authorizer/token_store/mock_database.go index e44cacaa7ef9fe59b3c31f5345cc5dd093a415c9..7878da8d43baecbb694a9d880a5c7db972444a2b 100644 --- a/authorizer/src/asapo_authorizer/database/mock_database.go +++ b/authorizer/src/asapo_authorizer/token_store/mock_database.go @@ -1,6 +1,6 @@ // +build !release -package database +package token_store import ( "github.com/stretchr/testify/mock" @@ -28,3 +28,23 @@ func (db *MockedDatabase) ProcessRequest(request Request, extraParams ...interfa args := db.Called(request,extraParams) return args.Get(0).([]byte), args.Error(1) } + + +type FakeDatabase struct { +} + +func (db *FakeDatabase) Connect(address string) error { + return nil +} + +func (db *FakeDatabase) Close() { + return +} + +func (db *FakeDatabase) Ping() error { + return nil +} + +func (db *FakeDatabase) ProcessRequest(request Request, extraParams ...interface{}) (answer []byte, err error) { + return nil,nil +} diff --git a/authorizer/src/asapo_authorizer/token_store/mock_store.go b/authorizer/src/asapo_authorizer/token_store/mock_store.go new file mode 100644 index 0000000000000000000000000000000000000000..b2db86bcbbf8bf0363310e0c75487631a2ff76d4 --- /dev/null +++ b/authorizer/src/asapo_authorizer/token_store/mock_store.go @@ -0,0 +1,43 @@ +// +build !release + +package token_store + +import ( + "github.com/stretchr/testify/mock" +) + +type MockedStore struct { + mock.Mock +} + +func (store *MockedStore) Init(db Agent) error { + args := store.Called(db) + return args.Error(0) +} + +func (store *MockedStore) AddToken(token TokenRecord) error { + args := store.Called(token) + return args.Error(0) +} + +func (store *MockedStore) RevokeToken(token string,id string) (TokenRecord, error) { + args := store.Called(token,id) + return args.Get(0).(TokenRecord), args.Error(1) +} + +func (store *MockedStore) GetTokenList() ([]TokenRecord,error) { + args := store.Called() + return args.Get(0).([]TokenRecord), args.Error(1) +} + +func (store *MockedStore) GetRevokedTokenIds() ([]string,error) { + args := store.Called() + return args.Get(0).([]string), args.Error(1) +} + +func (store *MockedStore) Close() { + store.Called() + return +} + + diff --git a/authorizer/src/asapo_authorizer/database/mongodb.go b/authorizer/src/asapo_authorizer/token_store/mongodb.go similarity index 62% rename from authorizer/src/asapo_authorizer/database/mongodb.go rename to authorizer/src/asapo_authorizer/token_store/mongodb.go index 51d69165d4adc2fce64e251c7b97513ca811a893..fbd1359125d9ad023f7074a909280b3e5a1b946b 100644 --- a/authorizer/src/asapo_authorizer/database/mongodb.go +++ b/authorizer/src/asapo_authorizer/token_store/mongodb.go @@ -1,11 +1,12 @@ //+build !test -package database +package token_store import ( "asapo_common/utils" "context" "errors" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -95,7 +96,6 @@ func duplicateError(err error) bool { return command_error.Name == "DuplicateKey" } - func (db *Mongodb) checkDatabaseOperationPrerequisites(request Request) error { if db.client == nil { return &DBError{utils.StatusServiceUnavailable, no_session_msg} @@ -125,8 +125,69 @@ func (db *Mongodb) createRecord(request Request, extra_params ...interface{}) ([ return nil, nil } +func (db *Mongodb) readRecords(request Request, extraParams ...interface{}) ([]byte, error) { + c := db.client.Database(request.DbName).Collection(request.Collection) + + if len(extraParams) != 1 { + return nil, errors.New("wrong number of parameters") + } + res := extraParams[0] + + opts := options.Find() + + cursor, err := c.Find(context.TODO(), bson.M{}, opts) + if err != nil { + return nil, err + } + + err = cursor.All(context.TODO(), res) + if err != nil { + return nil, err + } + return nil, nil +} + +func (db *Mongodb) readRecord(request Request, extraParams ...interface{}) ([]byte, error) { + c := db.client.Database(request.DbName).Collection(request.Collection) + + if len(extraParams) != 2 { + return nil, errors.New("wrong number of parameters") + } + q := extraParams[0] + res := extraParams[1] + err := c.FindOne(context.TODO(), q, options.FindOne()).Decode(res) + if err != nil { + return nil, err + } + return nil, nil +} + +func (db *Mongodb) updateRecord(request Request, extra_params ...interface{}) ([]byte, error) { + if len(extra_params) != 4 { + return nil, errors.New("wrong number of parameters") + } + id, ok := extra_params[0].(string) + if !ok { + return nil, errors.New("id must be string") + } + input := extra_params[1] + upsert, ok := extra_params[2].(bool) + if !ok { + return nil, errors.New("upsert must be string") + } + output := extra_params[3] + + opts := options.FindOneAndUpdate().SetUpsert(upsert).SetReturnDocument(options.After) + filter := bson.D{{"_id", id}} + + update := bson.D{{"$set", input}} + c := db.client.Database(request.DbName).Collection(request.Collection) + err := c.FindOneAndUpdate(context.TODO(), filter, update, opts).Decode(output) + return nil, err +} + -func (db *Mongodb) ProcessRequest(request Request,extraParams ...interface{}) (answer []byte, err error) { +func (db *Mongodb) ProcessRequest(request Request, extraParams ...interface{}) (answer []byte, err error) { dbClientLock.RLock() defer dbClientLock.RUnlock() @@ -137,6 +198,12 @@ func (db *Mongodb) ProcessRequest(request Request,extraParams ...interface{}) (a switch request.Op { case "create_record": return db.createRecord(request, extraParams...) + case "read_records": + return db.readRecords(request, extraParams...) + case "read_record": + return db.readRecord(request, extraParams...) + case "update_record": + return db.updateRecord(request, extraParams...) } return nil, errors.New("Wrong db operation: " + request.Op) diff --git a/authorizer/src/asapo_authorizer/token_store/token_store.go b/authorizer/src/asapo_authorizer/token_store/token_store.go new file mode 100644 index 0000000000000000000000000000000000000000..0de0d1c45c61a240079f478673ff3b829ce25774 --- /dev/null +++ b/authorizer/src/asapo_authorizer/token_store/token_store.go @@ -0,0 +1,243 @@ +package token_store + +import ( + "asapo_authorizer/common" + "asapo_common/discovery" + log "asapo_common/logger" + "asapo_common/utils" + "errors" + "net/http" + "sync" + "time" +) + +type Store interface { + Init(db Agent) error + AddToken(token TokenRecord) error + RevokeToken(token string, id string) (TokenRecord, error) + GetTokenList() ([]TokenRecord, error) + GetRevokedTokenIds() ([]string, error) + Close() +} + +type TokenStore struct { + db Agent + revokedTokenList struct { + tokens []string + lock sync.RWMutex + lastUpdate time.Time + lastError error + } + stopchan chan bool +} + +var discoveryService discovery.DiscoveryAPI + +func (store *TokenStore) reconnectDb() (dbaddress string, err error) { + if store.db == nil { + return "", errors.New("token_store not initialized") + } + store.db.Close() + return store.initDB() +} + +func (store *TokenStore) initDB() (dbaddress string, err error) { + dbaddress = common.Settings.DatabaseServer + if common.Settings.DatabaseServer == "auto" { + dbaddress, err = discoveryService.GetMongoDbAddress() + if err != nil { + return "", err + } + if dbaddress == "" { + return "", errors.New("no token_store servers found") + } + log.Debug("Got mongodb server: " + dbaddress) + } + return dbaddress, store.db.Connect(dbaddress) + +} + +func (store *TokenStore) reconnectIfNeeded(db_error error) { + code := GetStatusCodeFromError(db_error) + if code != utils.StatusServiceUnavailable { + return + } + + if dbaddress, err := store.reconnectDb(); err != nil { + log.Error("cannot reconnect to database: " + err.Error()) + } else { + log.Debug("reconnected to database at" + dbaddress) + } +} + +func createDiscoveryService() { + discoveryService = discovery.CreateDiscoveryService(&http.Client{}, "http://"+common.Settings.DiscoveryServer) +} + +func (store *TokenStore) Init(db Agent) error { + if db == nil { + store.db = new(Mongodb) + } else { + store.db = db + } + createDiscoveryService() + _, err := store.initDB() + store.stopchan = make(chan bool) + if common.Settings.UpdateRevokedTokensIntervalSec > 0 { + go store.loopGetRevokedTokens() + } + return err +} + +func (store *TokenStore) processError(err error) error { + if err != nil { + go store.reconnectIfNeeded(err) + } + return err +} + +func (store *TokenStore) AddToken(token TokenRecord) error { + _, err := store.db.ProcessRequest(Request{ + DbName: KAdminDb, + Collection: KTokens, + Op: "create_record", + }, &token) + return store.processError(err) +} + +func (store *TokenStore) findToken(token string, id string) (TokenRecord, error) { + q := map[string]interface{}{} + if token != "" { + q["token"] = token + } else { + q["_id"] = id + } + var record TokenRecord + _, err := store.db.ProcessRequest(Request{ + DbName: KAdminDb, + Collection: KTokens, + Op: "read_record", + }, q, &record) + if err != nil { + return TokenRecord{}, err + } + if record.Revoked { + return TokenRecord{}, errors.New("token already revoked") + } + return record, nil +} + +func (store *TokenStore) updateTokenStatus(token *TokenRecord) error { + _, err := store.db.ProcessRequest(Request{ + DbName: KAdminDb, + Collection: KTokens, + Op: "update_record", + }, token.Id, map[string]interface{}{"revoked": true}, false, token) + if err != nil { + return err + } + + idRec := IdRecord{token.Id} + _, err = store.db.ProcessRequest(Request{ + DbName: KAdminDb, + Collection: KRevokedTokens, + Op: "create_record", + }, &idRec) + return err +} + +func (store *TokenStore) updateRevokedTokensList(tokenId string) error { + idRec := IdRecord{tokenId} + _, err := store.db.ProcessRequest(Request{ + DbName: KAdminDb, + Collection: KRevokedTokens, + Op: "create_record", + }, &idRec) + return err +} + +func (store *TokenStore) RevokeToken(token string, id string) (TokenRecord, error) { + tokenRecord, err := store.findToken(token, id) + if err != nil { + return TokenRecord{}, store.processError(err) + } + + err = store.updateTokenStatus(&tokenRecord) + if err != nil { + return TokenRecord{}, store.processError(err) + } + + err = store.updateRevokedTokensList(tokenRecord.Id) + if err != nil { + return TokenRecord{}, store.processError(err) + } + + return tokenRecord, nil +} + +func (store *TokenStore) GetTokenList() ([]TokenRecord, error) { + var res []TokenRecord + _, err := store.db.ProcessRequest(Request{ + DbName: KAdminDb, + Collection: KTokens, + Op: "read_records", + }, &res) + return res, store.processError(err) +} + +func (store *TokenStore) loopGetRevokedTokens() { + next_update := 0 + for true { + var res []IdRecord + _, err := store.db.ProcessRequest(Request{ + DbName: KAdminDb, + Collection: KRevokedTokens, + Op: "read_records", + }, &res) + if err != nil { + store.revokedTokenList.lock.Lock() + store.revokedTokenList.lastError = err + store.processError(err) + store.revokedTokenList.lock.Unlock() + next_update = 1 + log.Error("cannot get revoked tokens: " + err.Error()) + } else { + log.Debug("received revoked tokens list") + next_update = common.Settings.UpdateRevokedTokensIntervalSec + tokens := make([]string, len(res)) + for i, token := range res { + tokens[i] = token.Id + } + store.revokedTokenList.lock.Lock() + store.revokedTokenList.tokens = tokens + store.revokedTokenList.lock.Unlock() + } + select { + case <-store.stopchan: + return + case <-time.After(time.Duration(next_update) * time.Second): + break + } + } +} + +func (store *TokenStore) GetRevokedTokenIds() ([]string, error) { + store.revokedTokenList.lock.RLock() + defer store.revokedTokenList.lock.RUnlock() + if store.revokedTokenList.lastError != nil { + return []string{}, store.revokedTokenList.lastError + } +// res := make([]string, len(store.revokedTokenList.tokens)) +// copy(res, store.revokedTokenList.tokens) +// return res,nil + return store.revokedTokenList.tokens, nil +} + +func (store *TokenStore) Close() { + if common.Settings.UpdateRevokedTokensIntervalSec > 0 { + store.stopchan <- true + } + if store.db != nil { + store.db.Close() + } +} diff --git a/authorizer/src/asapo_authorizer/token_store/token_store_test.go b/authorizer/src/asapo_authorizer/token_store/token_store_test.go new file mode 100644 index 0000000000000000000000000000000000000000..550784d6364b95e3369a5a2de3f75661e3059e71 --- /dev/null +++ b/authorizer/src/asapo_authorizer/token_store/token_store_test.go @@ -0,0 +1,146 @@ +package token_store + +import ( + "asapo_authorizer/common" + "asapo_common/logger" + "asapo_common/utils" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + "strings" + "testing" + "time" +) + +const expectedSource = "datasource" +const expectedStream = "stream" + +func ExpectReconnect(mock_db *MockedDatabase) { + mock_db.On("Close").Return() + mock_db.On("Connect", mock.Anything).Return(nil) +} + +func assertExpectations(t *testing.T, mock_db *MockedDatabase) { + mock_db.AssertExpectations(t) + mock_db.ExpectedCalls = nil + logger.MockLog.AssertExpectations(t) + logger.MockLog.ExpectedCalls = nil +} + + +type TokenStoreTestSuite struct { + suite.Suite + mock_db *MockedDatabase + store Store +} + +func (suite *TokenStoreTestSuite) SetupTest() { + common.Settings.UpdateRevokedTokensIntervalSec = 0 + suite.mock_db = new(MockedDatabase) + suite.store = new(TokenStore) + suite.mock_db.On("Connect", mock.Anything).Return( nil) + suite.store.Init(suite.mock_db) + logger.SetMockLog() +} + +func (suite *TokenStoreTestSuite) TearDownTest() { + assertExpectations(suite.T(), suite.mock_db) + logger.UnsetMockLog() + suite.store = nil +} + +func TestTokenStoreTestSuite(t *testing.T) { + suite.Run(t, new(TokenStoreTestSuite)) +} + +func containsMatcher(substr string) func(str string) bool { + return func(str string) bool { return strings.Contains(str, substr) } +} + +func (suite *TokenStoreTestSuite) TestProcessRequestWithConnectionError() { + ExpectReconnect(suite.mock_db) + suite.mock_db.On("ProcessRequest", mock.Anything, mock.Anything).Return([]byte(""), + &DBError{utils.StatusServiceUnavailable, ""}) + logger.MockLog.On("Debug", mock.MatchedBy(containsMatcher("reconnected"))) + + err := suite.store.AddToken(TokenRecord{}) + time.Sleep(time.Second) + suite.Error(err, "need reconnect") +} + + +func (suite *TokenStoreTestSuite) TestProcessRequestAddToken() { + req := Request{ + DbName: "asapo_admin", + Collection: "tokens", + Op: "create_record", + } + suite.mock_db.On("ProcessRequest", req, mock.Anything).Return([]byte(""), nil) + err := suite.store.AddToken(TokenRecord{}) + suite.Equal(err, nil, "ok") +} + + +func (suite *TokenStoreTestSuite) TestProcessRequestGetTokenList() { + req := Request{ + DbName: "asapo_admin", + Collection: "tokens", + Op: "read_records", + } + suite.mock_db.On("ProcessRequest", req, mock.Anything).Return([]byte(""), nil) + _,err := suite.store.GetTokenList() + suite.Equal(err, nil, "ok") +} + + +func (suite *TokenStoreTestSuite) TestProcessRequestRevokeToken() { + req := Request{ + DbName: "asapo_admin", + Collection: "tokens", + Op: "read_record", + } + expectedToken := TokenRecord{Id :"1234", Token:"token",Revoked: false} + expectedRevokedToken := expectedToken + expectedRevokedToken.Revoked = true + suite.mock_db.On("ProcessRequest", req,mock.Anything).Return([]byte(""), nil).Run(func(args mock.Arguments) { + rec := args.Get(1).([]interface{})[1].(*TokenRecord) + *rec = expectedToken + }) + + req = Request{ + DbName: KAdminDb, + Collection: KTokens, + Op: "update_record"} + suite.mock_db.On("ProcessRequest", req,mock.Anything).Return([]byte(""), nil).Run(func(args mock.Arguments) { + rec := args.Get(1).([]interface{})[3].(*TokenRecord) + *rec = expectedRevokedToken + }) + + req = Request{ + DbName: "asapo_admin", + Collection: "revoked_tokens", + Op: "create_record", + } + suite.mock_db.On("ProcessRequest", req, mock.Anything).Return([]byte(""), nil) + + token,err := suite.store.RevokeToken(expectedToken.Token,"") + suite.Equal(err, nil, "ok") + suite.Equal(token, expectedRevokedToken, "ok") +} + +func (suite *TokenStoreTestSuite) TestProcessRequestGetRevokedTokens() { + suite.mock_db.On("Close") + suite.store.Close() + common.Settings.UpdateRevokedTokensIntervalSec = 5 + suite.store.Init(suite.mock_db) + req := Request{ + DbName: "asapo_admin", + Collection: "revoked_tokens", + Op: "read_records", + } + suite.mock_db.On("ProcessRequest", req, mock.Anything).Return([]byte(""), nil) + + logger.MockLog.On("Debug", mock.MatchedBy(containsMatcher("list"))) + time.Sleep(time.Second*1) + _,err := suite.store.GetRevokedTokenIds() + suite.Equal(err, nil, "ok") +} \ No newline at end of file diff --git a/deploy/build_env/centos/build.sh b/deploy/build_env/centos/build.sh index 0f000741de74a14b217b875804e5059d0316de35..103ae9e77c95a0761b89a43d3e2114d1beb604fd 100755 --- a/deploy/build_env/centos/build.sh +++ b/deploy/build_env/centos/build.sh @@ -24,6 +24,7 @@ cmake \ -DBUILD_CLIENTS_ONLY=ON \ -DNUMPY_VERSION=0 \ -DBUILD_PYTHON=ON \ + -DPACKAGE_RELEASE_SUFFIX=1.$OS \ -DBUILD_PYTHON_PACKAGES="source;rpm" \ -DBUILD_PYTHON_DOCS=$BUILD_PYTHON_DOCS \ .. diff --git a/deploy/build_env/debians/build.sh b/deploy/build_env/debians/build.sh index e78771d5ca95635f17a6dac13619390a4128fec0..9e27fc1ee4cdad925988a7b758854c4ab6804f04 100755 --- a/deploy/build_env/debians/build.sh +++ b/deploy/build_env/debians/build.sh @@ -34,6 +34,7 @@ cmake \ -DBUILD_CLIENTS_ONLY=ON \ -DNUMPY_VERSION=0 \ -DBUILD_PYTHON=ON \ + -DPACKAGE_RELEASE_SUFFIX=$OS \ -DBUILD_PYTHON_PACKAGES="source;deb" \ -DBUILD_PYTHON_DOCS=$BUILD_PYTHON_DOCS \ ..