diff --git a/CHANGELOG.md b/CHANGELOG.md index bbbc7b6bc39bf254cde220402470f92b18dc8a28..bb02fdd1493bbbb1c3a376c7761db01fa4a605aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,19 @@ ## 21.09.0 (in progress) FEATURES -* Producer API: C client +* Producer API: C client +* Introduce a token to send data in "raw" mode without LDAP authorization IMPROVEMENTS * Allow using ASAPO for commissioning beamtimes +* Implement token revocation + +BUG FIXES +* Consumer/Producer API: fixed bug with "_" in stream name + +INTERNAL +* Improved authoriation service caching +* Added profiling for Go services ## 21.06.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index fcdc271fbc47de17adc767cf2939b496155a054a..36302965720bf9265d21426f3afa9bb1aba779f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,12 +9,12 @@ set (ASAPO_EXAMPLES_DIR .) #protocol version changes if one of the microservice API's change set (ASAPO_CONSUMER_PROTOCOL "v0.4") -set (ASAPO_PRODUCER_PROTOCOL "v0.3") +set (ASAPO_PRODUCER_PROTOCOL "v0.4") set (ASAPO_DISCOVERY_API_VER "v0.1") set (ASAPO_AUTHORIZER_API_VER "v0.2") set (ASAPO_BROKER_API_VER "v0.4") set (ASAPO_FILE_TRANSFER_SERVICE_API_VER "v0.2") -set (ASAPO_RECEIVER_API_VER "v0.3") +set (ASAPO_RECEIVER_API_VER "v0.4") set (ASAPO_RDS_API_VER "v0.1") set (DB_SCHEMA_VER "v0.1") diff --git a/CMakeModules/testing_cpp.cmake b/CMakeModules/testing_cpp.cmake index 23a4457ac3af624949eeba0f4b3c9a55900820c3..63dae68e4cf2c706b2a245cbbbd8ef8de9419126 100644 --- a/CMakeModules/testing_cpp.cmake +++ b/CMakeModules/testing_cpp.cmake @@ -4,6 +4,7 @@ endif () set (TOKENS "ASAPO_TEST_RW_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjMTkyMXJqaXB0MzVja3MzYTEwZyIsInN1YiI6ImJ0X2FzYXBvX3Rlc3QiLCJFeHRyYUNsYWltcyI6eyJBY2Nlc3NUeXBlcyI6WyJyZWFkIiwid3JpdGUiXX19.3PFdG0f48yKrOyJwPErYcewpcbZgnd8rBmBphw_kdJ0") set (TOKENS "${TOKENS};ASAPO_CREATE_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjMTkyYzMzaXB0Mzdkb3IzYmZjZyIsInN1YiI6ImFkbWluIiwiRXh0cmFDbGFpbXMiOnsiQWNjZXNzVHlwZXMiOlsiY3JlYXRlIl19fQ.AI41cZ7dZL0g-rrdKIQgd7ijjzuyH1Fm0xojCXwLNBo") +set (TOKENS "${TOKENS};ASAPO_REVOKE_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjNWFxOHVyaXB0MzV0aG9raDFwMCIsInN1YiI6ImFkbWluIiwiRXh0cmFDbGFpbXMiOnsiQWNjZXNzVHlwZXMiOlsicmV2b2tlIl19fQ.GNje7w6biX0-ynltRr81p5SBSWwmKdDwGfs-adb094Q") set (TOKENS "${TOKENS};C20180508_000_COM20181_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjMTkyaDRiaXB0Mzd1cGo1aDdlMCIsInN1YiI6ImJ0X2MyMDE4MDUwOC0wMDAtQ09NMjAxODEiLCJFeHRyYUNsYWltcyI6eyJBY2Nlc3NUeXBlcyI6WyJyZWFkIiwid3JpdGUiXX19.yONpjW2ybZMc9E9Eu4Hmn1roVR-mxf2OQQyXfnel5C8") set (TOKENS "${TOKENS};BT11000015_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjMTkyajZqaXB0MzA3aHU1amwxZyIsInN1YiI6ImJ0XzExMDAwMDE1IiwiRXh0cmFDbGFpbXMiOnsiQWNjZXNzVHlwZXMiOlsicmVhZCJdfX0.kVs669HAS4sj9VAZk8pWTLrYNQp46mOnH4id4-_qd9g") set (TOKENS "${TOKENS};BT11000016_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjMTkyajQzaXB0MzA3OWxwc3Z2ZyIsInN1YiI6ImJ0XzExMDAwMDE2IiwiRXh0cmFDbGFpbXMiOnsiQWNjZXNzVHlwZXMiOlsicmVhZCJdfX0.mpTVGtcdR0l4NaeHFTf16iWrfMYaLzh2pAjN5muil6Q") diff --git a/PROTOCOL-VERSIONS.md b/PROTOCOL-VERSIONS.md index e72696805562d6e3d50eb4b6f713be8b7831b33b..3b87af27833e8b8bfeab57e857ca531cac18a631 100644 --- a/PROTOCOL-VERSIONS.md +++ b/PROTOCOL-VERSIONS.md @@ -1,7 +1,8 @@ ### Producer Protocol | Release | used by client | Supported by server | Status | | ------------ | ------------------- | -------------------- | ---------------- | -| v0.3 | 21.06.0 - 21.06.0 | 21.06.0 - 21.09.0 | Current version | +| v0.4 | 21.09.0 - 21.09.0 | 21.09.0 - 21.09.0 | Current version | +| v0.3 | 21.06.0 - 21.06.0 | 21.06.0 - 21.09.0 | Deprecates from 01.09.2022 | | v0.2 | 21.03.2 - 21.03.2 | 21.03.2 - 21.09.0 | Deprecates from 01.07.2022 | | v0.1 | 21.03.0 - 21.03.1 | 21.03.0 - 21.09.0 | Deprecates from 01.06.2022 | @@ -9,7 +10,7 @@ ### Consumer Protocol | Release | used by client | Supported by server | Status | | ------------ | ------------------- | -------------------- | ---------------- | -| v0.4 | 21.06.0 - 21.06.0 | 21.06.0 - 21.09.0 | Current version | +| v0.4 | 21.06.0 - 21.09.0 | 21.06.0 - 21.09.0 | Current version | | v0.3 | 21.03.3 - 21.03.3 | 21.03.3 - 21.09.0 | Deprecates from 01.07.2022 | | v0.2 | 21.03.2 - 21.03.2 | 21.03.2 - 21.09.0 | Deprecates from 01.06.2022 | | v0.1 | 21.03.0 - 21.03.1 | 21.03.0 - 21.09.0 | Deprecates from 01.06.2022 | diff --git a/VERSIONS.md b/VERSIONS.md index a52e3d2571f0d5faf4a6646ff2693fc75bfcbf2f..05d9509333bb681295b0a3f1a6c4f852e62c49d7 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -2,8 +2,8 @@ | Release | API changed\*\* | Protocol | Supported by server from/to | Status |Comment| | ------------ | ----------- | -------- | ------------------------- | --------------------- | ------- | -| 21.09.0 | No | v0.3 | 21.06.0/21.09.0 | current version || -| 21.06.0 | Yes | v0.3 | 21.06.0/21.09.0 | |arbitrary characters| +| 21.09.0 | No | v0.4 | 21.09.0/21.09.0 | current version |beamline token for raw | +| 21.06.0 | Yes | v0.3 | 21.06.0/21.09.0 | deprecates 01.09.2022 |arbitrary characters| | 21.03.3 | No | v0.2 | 21.03.2/21.09.0 | deprecates 01.07.2022 |bugfix in server| | 21.03.2 | Yes | v0.2 | 21.03.2/21.09.0 | deprecates 01.07.2022 |bugfixes, add delete_stream| | 21.03.1 | No | v0.1 | 21.03.0/21.09.0 | deprecates 01.06.2022 |bugfix in server| diff --git a/authorizer/src/asapo_authorizer/authorization/authorization.go b/authorizer/src/asapo_authorizer/authorization/authorization.go index 1a0e85b25a6041b26e69a87705f36732f96a5838..59b613c6ff9e6ed3b7c227d307c39672c89ccaca 100644 --- a/authorizer/src/asapo_authorizer/authorization/authorization.go +++ b/authorizer/src/asapo_authorizer/authorization/authorization.go @@ -14,8 +14,8 @@ type Auth struct { authJWT utils.Auth } -func NewAuth(authUser,authAdmin,authJWT utils.Auth) *Auth { - return &Auth{authUser,authAdmin,authJWT} +func NewAuth(authUser, authAdmin, authJWT utils.Auth) *Auth { + return &Auth{authUser, authAdmin, authJWT} } func (auth *Auth) AdminAuth() utils.Auth { @@ -31,7 +31,7 @@ func (auth *Auth) JWTAuth() utils.Auth { } func subjectFromRequest(request structs.IssueTokenRequest) string { - for key,value := range request.Subject { + for key, value := range request.Subject { switch key { case "beamline": return utils.SubjectFromBeamline(value) @@ -44,10 +44,10 @@ func subjectFromRequest(request structs.IssueTokenRequest) string { return "" } -func (auth *Auth) PrepareAccessToken(request structs.IssueTokenRequest, userToken bool) (string, error) { - var claims utils.CustomClaims +func (auth *Auth) PrepareAccessToken(request structs.IssueTokenRequest, userToken bool) (token string, claims *utils.CustomClaims, err error) { var extraClaim structs.AccessTokenExtraClaim + claims = new(utils.CustomClaims) claims.Subject = subjectFromRequest(request) extraClaim.AccessTypes = request.AccessTypes @@ -57,15 +57,19 @@ func (auth *Auth) PrepareAccessToken(request structs.IssueTokenRequest, userToke claims.Id = uid.String() if userToken { - return auth.UserAuth().GenerateToken(&claims) + token, err = auth.UserAuth().GenerateToken(claims) } else { - return auth.AdminAuth().GenerateToken(&claims) + token, err = auth.AdminAuth().GenerateToken(claims) } + if err != nil { + return "", nil, err + } + return } func UserTokenResponce(request structs.IssueTokenRequest, token string) []byte { expires := "" - if request.DaysValid>0 { + if request.DaysValid > 0 { expires = time.Now().Add(time.Duration(request.DaysValid*24) * time.Hour).UTC().Format(time.RFC3339) } answer := structs.IssueTokenResponse{ diff --git a/authorizer/src/asapo_authorizer/cli/cli.go b/authorizer/src/asapo_authorizer/cli/cli.go index 0851d568c782b2d97f80894b3afd280f0c5a1b64..e39bbd65638b2cfc93c02ec9023870f1d4acd5b9 100644 --- a/authorizer/src/asapo_authorizer/cli/cli.go +++ b/authorizer/src/asapo_authorizer/cli/cli.go @@ -3,6 +3,7 @@ package cli import ( + "asapo_authorizer/token_store" "errors" "flag" "fmt" @@ -16,6 +17,8 @@ var flHelp bool var outBuf io.Writer = os.Stdout +var store token_store.Store + func printHelp(f *flag.FlagSet) bool { if flHelp { f.Usage() @@ -39,6 +42,10 @@ func DoCommand(name string, args []string) error { method := methodVal.Interface().(func() error) + 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 ba06241279ffc28371ed2021fd982ba11cfdd9c1..4d267a204f3f2fe4caed814e5e406fac8b33cfe8 100644 --- a/authorizer/src/asapo_authorizer/cli/create_token.go +++ b/authorizer/src/asapo_authorizer/cli/create_token.go @@ -3,11 +3,13 @@ package cli import ( "asapo_authorizer/authorization" "asapo_authorizer/server" + "asapo_authorizer/token_store" "asapo_common/structs" "errors" "fmt" "os" "strings" + "time" ) type tokenFlags struct { @@ -19,21 +21,26 @@ type tokenFlags struct { } func userTokenRequest(flags tokenFlags) (request structs.IssueTokenRequest, err error) { - if (flags.Beamline=="" && flags.Beamtime=="") || (flags.Beamline!="" && flags.Beamtime!="") { - return request,errors.New("beamtime or beamline must be set") + if (flags.Beamline == "" && flags.Beamtime == "") || (flags.Beamline != "" && flags.Beamtime != "") { + return request, errors.New("beamtime or beamline must be set") } - request.Subject = make(map[string]string,1) - if (flags.Beamline!="") { - request.Subject["beamline"]=flags.Beamline + request.Subject = make(map[string]string, 1) + if flags.Beamline != "" { + request.Subject["beamline"] = flags.Beamline } else { - request.Subject["beamtimeId"]=flags.Beamtime + request.Subject["beamtimeId"] = flags.Beamtime } - request.AccessTypes = strings.Split(flags.AccessType,",") - for _,at:=range request.AccessTypes { - if at!="read" && at!="write" { - return request,errors.New("access type must be read of write") + request.AccessTypes = strings.Split(flags.AccessType, ",") + for _, at := range request.AccessTypes { + if at != "read" && at != "write" && !(at == "writeraw" && request.Subject["beamline"] != "") { + if request.Subject["beamline"] != "" { + return request, errors.New("access type must be read, write or writeraw") + } else { + return request, errors.New("access type must be read or write") + } + } } @@ -42,21 +49,20 @@ func userTokenRequest(flags tokenFlags) (request structs.IssueTokenRequest, err return } - func adminTokenRequest(flags tokenFlags) (request structs.IssueTokenRequest, err error) { - if flags.Beamline+flags.Beamtime!="" { - return request,errors.New("beamtime and beamline must not be set for admin token") + if flags.Beamline+flags.Beamtime != "" { + return request, errors.New("beamtime and beamline must not be set for admin token") } - request.AccessTypes = strings.Split(flags.AccessType,",") - for _,at:=range request.AccessTypes { - if at!="create" && at!="revoke" && at!="list" { - return request,errors.New("access type must be create,revoke of list") + request.AccessTypes = strings.Split(flags.AccessType, ",") + for _, at := range request.AccessTypes { + if at != "create" && at != "revoke" && at != "list" { + return request, errors.New("access type must be create,revoke of list") } } - request.Subject = make(map[string]string,1) - request.Subject["user"]="admin" + request.Subject = make(map[string]string, 1) + request.Subject["user"] = "admin" request.DaysValid = flags.DaysValid return @@ -78,7 +84,14 @@ func (cmd *command) CommandCreate_token() (err error) { return err } - token, err := server.Auth.PrepareAccessToken(request,userToken) + token, claims, err := server.Auth.PrepareAccessToken(request, userToken) + if err != nil { + return err + } + 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 } @@ -100,12 +113,11 @@ func getTokenRequest(flags tokenFlags) (request structs.IssueTokenRequest, userT return structs.IssueTokenRequest{}, false, errors.New("wrong token type") } if err != nil { - return structs.IssueTokenRequest{},false, err + return structs.IssueTokenRequest{}, false, err } return request, userToken, err } - func (cmd *command) parseTokenFlags(message_string string) (tokenFlags, error) { var flags tokenFlags @@ -113,10 +125,9 @@ func (cmd *command) parseTokenFlags(message_string string) (tokenFlags, error) { flagset.StringVar(&flags.Type, "type", "", "token type") flagset.StringVar(&flags.Beamtime, "beamtime", "", "beamtime for user token") flagset.StringVar(&flags.Beamline, "beamline", "", "beamline for user token") - flagset.StringVar(&flags.AccessType, "access-types", "", "read/write for user token") + flagset.StringVar(&flags.AccessType, "access-types", "", "read/write/writeraw(beamline only) for user token") flagset.IntVar(&flags.DaysValid, "duration-days", 0, "token duration (in days)") - flagset.Parse(cmd.args) if printHelp(flagset) { @@ -124,10 +135,9 @@ func (cmd *command) parseTokenFlags(message_string string) (tokenFlags, error) { } if flags.Type == "" { - return flags, errors.New("secret file missed ") + return flags, errors.New("access types missing") } - return flags, nil } diff --git a/authorizer/src/asapo_authorizer/cli/create_token_test.go b/authorizer/src/asapo_authorizer/cli/create_token_test.go index ca16f199ed5c20d2a0f0946c76bd00c06d168778..bedd7476bfdbcecfcc1dda25ad6436bc46a7eb44 100644 --- a/authorizer/src/asapo_authorizer/cli/create_token_test.go +++ b/authorizer/src/asapo_authorizer/cli/create_token_test.go @@ -3,9 +3,11 @@ package cli import ( "asapo_authorizer/authorization" "asapo_authorizer/server" + "asapo_authorizer/token_store" "asapo_common/structs" "asapo_common/utils" "encoding/json" + "github.com/stretchr/testify/mock" "testing" "bytes" @@ -41,13 +43,22 @@ var tokenTests = []struct { func TestGenerateToken(t *testing.T) { server.Auth = authorization.NewAuth(utils.NewJWTAuth("secret_user"),utils.NewJWTAuth("secret_admin"),utils.NewJWTAuth("secret")) + mock_store := new(token_store.MockedStore) + store = mock_store + for _, test := range tokenTests { outBuf = new(bytes.Buffer) + + if test.ok { + mock_store.On("AddToken", mock.Anything).Return(nil) + } + err := test.cmd.CommandCreate_token() if !test.ok { assert.NotNil(t, err, test.msg) continue } + assert.Nil(t, err, test.msg) var token structs.IssueTokenResponse json.Unmarshal(outBuf.(*bytes.Buffer).Bytes(), &token) @@ -63,5 +74,9 @@ func TestGenerateToken(t *testing.T) { } else { assert.Empty(t, token.Expires, test.msg) } + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..923cc8f8b622b38023b1c256f190bce8faf478c9 --- /dev/null +++ b/authorizer/src/asapo_authorizer/cli/list_tokens.go @@ -0,0 +1,23 @@ +package cli + +import ( + "encoding/json" + "fmt" +) + +func (cmd *command) CommandList_tokens() (err error) { + message_string := "List tokens" + if cmd.description(message_string) { + return nil + } + + tokens,err := store.GetTokenList() + if err != nil { + return err + } + + answer,_ := json.Marshal(tokens) + fmt.Fprintf(outBuf, "%s\n", string(answer)) + return 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/go.mod b/authorizer/src/asapo_authorizer/go.mod index 67af7e095034c70910d408c70222786da84d7b83..81f46fd127f9573baccc9992a45bcedc15ce2812 100644 --- a/authorizer/src/asapo_authorizer/go.mod +++ b/authorizer/src/asapo_authorizer/go.mod @@ -9,5 +9,6 @@ require ( github.com/go-ldap/ldap v3.0.3+incompatible github.com/rs/xid v1.2.1 github.com/stretchr/testify v1.7.0 + go.mongodb.org/mongo-driver v1.7.2 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect ) diff --git a/authorizer/src/asapo_authorizer/go.sum b/authorizer/src/asapo_authorizer/go.sum index 4e1c32df6dd6e14736ec4b0bf11f74f6a56b7ca0..f9db70a3f9f6979312428952e43576663fda2592 100644 --- a/authorizer/src/asapo_authorizer/go.sum +++ b/authorizer/src/asapo_authorizer/go.sum @@ -1,3 +1,4 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -5,26 +6,132 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk= github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g= github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU= github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +go.mongodb.org/mongo-driver v1.7.2 h1:pFttQyIiJUHEn50YfZgC9ECjITMT44oiN36uArf/OFg= +go.mongodb.org/mongo-driver v1.7.2/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/authorizer/src/asapo_authorizer/server/authorize.go b/authorizer/src/asapo_authorizer/server/authorize.go index bac5853d8ad5a547c93e7c359fa71319112ed8e5..1afe3d9da5fb11154a68c6bd0896b1601aaa8e7e 100644 --- a/authorizer/src/asapo_authorizer/server/authorize.go +++ b/authorizer/src/asapo_authorizer/server/authorize.go @@ -14,9 +14,9 @@ import ( type SourceCredentials struct { BeamtimeId string Beamline string - DataSource string + DataSource string Token string - Type string + Type string } type authorizationRequest struct { @@ -26,15 +26,14 @@ type authorizationRequest struct { func getSourceCredentials(request authorizationRequest) (SourceCredentials, error) { - vals := strings.Split(request.SourceCredentials, "%") - nvals:=len(vals) + nvals := len(vals) if nvals < 5 { return SourceCredentials{}, errors.New("cannot get source credentials from " + request.SourceCredentials) } - creds := SourceCredentials{Type:vals[0], BeamtimeId: vals[1], Beamline: vals[2], Token:vals[nvals-1]} - creds.DataSource=strings.Join(vals[3:nvals-1],"%") + creds := SourceCredentials{Type: vals[0], BeamtimeId: vals[1], Beamline: vals[2], Token: vals[nvals-1]} + creds.DataSource = strings.Join(vals[3:nvals-1], "%") if creds.DataSource == "" { creds.DataSource = "detector" } @@ -54,27 +53,26 @@ func getSourceCredentials(request authorizationRequest) (SourceCredentials, erro return creds, nil } - func splitHost(hostPort string) string { s := strings.Split(hostPort, ":") 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,49 +80,59 @@ 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) { + cachedMetas.lock.Lock() + meta, ok := cachedMetas.cache[beamtime_id] + cachedMetas.lock.Unlock() + if ok { + return meta, nil + } + 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) { + for _, match := range matches { btInfo, err := beamtimeMetaFromMatch(match) if err != nil { continue } if btInfo.BeamtimeId == beamtime_id { + cachedMetas.lock.Lock() + cachedMetas.cache[beamtime_id] = btInfo + cachedMetas.lock.Unlock() 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){ +func findMetaFileInFolder(beamline string, iscommissioning bool) (string, string, error) { sep := string(filepath.Separator) - var pattern,folder string + var pattern, folder string if !iscommissioning { pattern = "beamtime-metadata-*.json" folder = "current" @@ -132,22 +140,22 @@ 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 + return "", "", err } if len(matches) != 1 { - return "","", errors.New("should be one beamtime-metadata file in folder") + return "", "", errors.New("should be one beamtime-metadata file in folder") } - return matches[0],online_path, nil + return matches[0], online_path, nil } -func findBeamtimeMetaFromBeamline(beamline string,iscommissioning bool) (meta beamtimeMeta, err error) { - fName,online_path, err := findMetaFileInFolder(beamline,iscommissioning) - if (err != nil) { - return beamtimeMeta{}, err +func findBeamtimeMetaFromBeamline(beamline string, iscommissioning bool) (meta common.BeamtimeMeta, err error) { + fName, online_path, err := findMetaFileInFolder(beamline, iscommissioning) + if err != nil { + return common.BeamtimeMeta{}, err } if iscommissioning { @@ -155,103 +163,119 @@ func findBeamtimeMetaFromBeamline(beamline string,iscommissioning bool) (meta be } else { meta, err = beamtimeMetaFromJson(fName) } - if (err != nil) { - return beamtimeMeta{}, err + if err != nil { + return common.BeamtimeMeta{}, err } - if meta.BeamtimeId == "" || meta.OfflinePath=="" || meta.Beamline == ""{ - return beamtimeMeta{}, errors.New("cannot set meta fields from beamtime file") + if meta.BeamtimeId == "" || meta.OfflinePath == "" || meta.Beamline == "" { + 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 - pair.AccessTypes = []string{"read","write"} + pair.AccessTypes = []string{"read", "write"} 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) +func authorizeByHost(host_ip, beamline string) error { + 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 } - if (!utils.StringInSlice(splitHost(host_ip),allowed_ips)) { - err_string := "beamine " +beamline+" not allowed for host " + host_ip + if !utils.StringInSlice(splitHost(host_ip), allowed_ips) { + err_string := "beamine " + beamline + " not allowed for host " + host_ip log.Error(err_string) return errors.New(err_string) } return nil } -func needHostAuthorization(creds SourceCredentials) bool { - return creds.Type == "raw" || len(creds.Token) == 0 +func canUseHostAuthorization(creds SourceCredentials) bool { + return len(creds.Token) == 0 +} + +func checkTokenRevoked(tokenId string) (err error) { + revoked, err := store.IsTokenRevoked(tokenId) + if err != nil { + return &common.ServerError{utils.StatusServiceUnavailable, err.Error()} + } + if revoked { + return errors.New("token was revoked") + } + return nil } func checkToken(token string, subject_expect string) (accessTypes []string, err error) { var extra_claim structs.AccessTokenExtraClaim - subject,err := Auth.UserAuth().CheckAndGetContent(token,&extra_claim) - if err!=nil { - return nil,err + claim, err := Auth.UserAuth().CheckAndGetContent(token, &extra_claim) + if err != nil { + return nil, err } - if extra_claim.AccessTypes==nil || len(extra_claim.AccessTypes)==0 { - return nil,errors.New("missing access types") + err = checkTokenRevoked(claim.Id) + if err != nil { + return nil, err + } + + if extra_claim.AccessTypes == nil || len(extra_claim.AccessTypes) == 0 { + return nil, errors.New("missing access types") } - if subject!=subject_expect { - return nil,errors.New("wrong token for "+subject_expect) + if claim.Subject != subject_expect { + return nil, errors.New("wrong token for " + subject_expect) } - return extra_claim.AccessTypes,err + return extra_claim.AccessTypes, err } func authorizeByToken(creds SourceCredentials) (accessTypes []string, err error) { - subject_expect:="" - if (creds.BeamtimeId != "auto") { + subject_expect := "" + if creds.BeamtimeId != "auto" { subject_expect = utils.SubjectFromBeamtime(creds.BeamtimeId) } else { subject_expect = utils.SubjectFromBeamline(creds.Beamline) } - return checkToken(creds.Token,subject_expect) + return checkToken(creds.Token, subject_expect) } func iscommissioning(beamtime string) bool { - return len(beamtime)>0 && beamtime[0]=='c' + 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 - if (creds.BeamtimeId != "auto") { + var meta common.BeamtimeMeta + if creds.BeamtimeId != "auto" { meta, err = findBeamtimeInfoFromId(creds.BeamtimeId) - if (err == nil ) { - meta_onilne, err_online := findBeamtimeMetaFromBeamline(meta.Beamline,iscommissioning(creds.BeamtimeId)) + if err == nil { + meta_onilne, err_online := findBeamtimeMetaFromBeamline(meta.Beamline, iscommissioning(creds.BeamtimeId)) if err_online == nil && meta.BeamtimeId == meta_onilne.BeamtimeId { meta.OnlinePath = meta_onilne.OnlinePath } } } else { - meta, err = findBeamtimeMetaFromBeamline(creds.Beamline,false) + meta, err = findBeamtimeMetaFromBeamline(creds.Beamline, false) } if creds.Type == "processed" { meta.OnlinePath = "" } - if (err != nil) { + if err != nil { log.Error(err.Error()) - return beamtimeMeta{}, err + return common.BeamtimeMeta{}, err } meta.DataSource = creds.DataSource @@ -260,84 +284,116 @@ 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" + if creds.Type == "raw" && meta.OnlinePath == "" { + err_string := "beamtime " + meta.BeamtimeId + " is not online" log.Error(err_string) - return nil,errors.New(err_string) + return nil, errors.New(err_string) } if creds.Beamline != "auto" && meta.Beamline != creds.Beamline { err_string := "given beamline (" + creds.Beamline + ") does not match the found one (" + meta.Beamline + ")" log.Debug(err_string) - return nil,errors.New(err_string) + return nil, errors.New(err_string) } - if needHostAuthorization(creds) { + if canUseHostAuthorization(creds) { if err := authorizeByHost(request.OriginHost, meta.Beamline); err != nil { - return nil,err + return nil, err + } + if creds.Type == "raw" { + accessTypes = []string{"read", "write", "writeraw"} + } else { + accessTypes = []string{"read", "write"} } - accessTypes = []string{"read","write"} } else { - accessTypes,err = authorizeByToken(creds) + accessTypes, err = authorizeByToken(creds) } - return accessTypes,err + 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 - log.Debug("authorized creds bl/bt: ", creds.Beamline+"/"+creds.BeamtimeId+", beamtime " + meta.BeamtimeId + " for " + request.OriginHost + " in " + - meta.Beamline+", type "+meta.Type, "online path "+meta.OnlinePath + ", offline path "+meta.OfflinePath) + log.Debug("authorized creds bl/bt: ", creds.Beamline+"/"+creds.BeamtimeId+", beamtime "+meta.BeamtimeId+" for "+request.OriginHost+" in "+ + meta.Beamline+", type "+meta.Type, "online path "+meta.OnlinePath+", offline path "+meta.OfflinePath) return meta, nil } +func writeServerError(w http.ResponseWriter, err error) { + serr, ok := err.(*common.ServerError) + if ok { + utils.WriteServerError(w, err, serr.Code) + return + } + utils.WriteServerError(w, err, http.StatusUnauthorized) + return +} + func routeAuthorize(w http.ResponseWriter, r *http.Request) { var request authorizationRequest - err := utils.ExtractRequest(r,&request) + err := utils.ExtractRequest(r, &request) if err != nil { - utils.WriteServerError(w,err,http.StatusBadRequest) + utils.WriteServerError(w, err, http.StatusBadRequest) return } creds, err := getSourceCredentials(request) if err != nil { - utils.WriteServerError(w,err,http.StatusBadRequest) + utils.WriteServerError(w, err, http.StatusBadRequest) return } beamtimeInfo, err := authorize(request, creds) - if (err != nil) { - serr,ok:=err.(*common.ServerError) - if ok { - utils.WriteServerError(w,err,serr.Code) - return - } - utils.WriteServerError(w,err,http.StatusUnauthorized) + if err != nil { + writeServerError(w, err) return } res, err := utils.MapToJson(&beamtimeInfo) if err != nil { - utils.WriteServerError(w,err,http.StatusInternalServerError) + utils.WriteServerError(w, err, http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write([]byte(res)) } + +func checkRole(w http.ResponseWriter, r *http.Request, role string) error { + var extraClaim structs.AccessTokenExtraClaim + var claims *utils.CustomClaims + if err := utils.JobClaimFromContext(r, &claims, &extraClaim); err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return err + } + + if err := checkTokenRevoked(claims.Id); err != nil { + writeServerError(w, err) + return err + } + + if claims.Subject != "admin" || !utils.StringInSlice(role, extraClaim.AccessTypes) { + err_txt := "wrong token claims" + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err_txt)) + return errors.New(err_txt) + } + return nil +} diff --git a/authorizer/src/asapo_authorizer/server/authorize_test.go b/authorizer/src/asapo_authorizer/server/authorize_test.go index f4e97e788fda426fc463b3019d69d1b857f75925..1c6e6400554b5ee6fe8fbc6dd324b8ad1ae7d346 100644 --- a/authorizer/src/asapo_authorizer/server/authorize_test.go +++ b/authorizer/src/asapo_authorizer/server/authorize_test.go @@ -4,6 +4,7 @@ import ( "asapo_authorizer/authorization" "asapo_authorizer/common" "asapo_authorizer/ldap_client" + "asapo_authorizer/token_store" "asapo_common/structs" "asapo_common/utils" "github.com/stretchr/testify/assert" @@ -16,20 +17,22 @@ import ( "testing" ) +var expectedTokenId = "123" -func prepareUserToken(payload string, accessTypes []string) string{ - auth := authorization.NewAuth(nil,utils.NewJWTAuth("secret_user"),nil) +func prepareAsapoToken(payload string, accessTypes []string) string { + auth := authorization.NewAuth(nil, utils.NewJWTAuth("secret_user"), nil) var claims utils.CustomClaims var extraClaim structs.AccessTokenExtraClaim claims.Subject = payload + claims.Id = expectedTokenId extraClaim.AccessTypes = accessTypes claims.ExtraClaims = &extraClaim token, _ := auth.AdminAuth().GenerateToken(&claims) return token } -func prepareAdminToken(payload string) string{ - auth:= authorization.NewAuth(nil,utils.NewJWTAuth("secret_admin"),nil) +func prepareAdminToken(payload string) string { + auth := authorization.NewAuth(nil, utils.NewJWTAuth("secret_admin"), nil) var claims utils.CustomClaims var extraClaim structs.AccessTokenExtraClaim @@ -42,7 +45,6 @@ func prepareAdminToken(payload string) string{ var mockClient = new(ldap_client.MockedLdapClient) - type request struct { path string cmd string @@ -50,11 +52,10 @@ type request struct { message string } -func allowBeamlines(beamlines []beamtimeMeta) { - settings.AlwaysAllowedBeamtimes=beamlines +func allowBeamlines(beamlines []common.BeamtimeMeta) { + common.Settings.AlwaysAllowedBeamtimes = beamlines } - func containsMatcher(substr string) func(str string) bool { return func(str string) bool { return strings.Contains(str, substr) } } @@ -64,56 +65,56 @@ func makeRequest(request interface{}) string { return string(buf) } -func doPostRequest(path string,buf string,authHeader string) *httptest.ResponseRecorder { +func doPostRequest(path string, buf string, authHeader string) *httptest.ResponseRecorder { mux := utils.NewRouter(listRoutes) req, _ := http.NewRequest("POST", path, strings.NewReader(buf)) - if authHeader!="" { - req.Header.Add("Authorization",authHeader) + if authHeader != "" { + req.Header.Add("Authorization", authHeader) } w := httptest.NewRecorder() mux.ServeHTTP(w, req) return w } -var credTests = [] struct { +var credTests = []struct { request string - cred SourceCredentials - ok bool + cred SourceCredentials + ok bool message string -} { - {"processed%asapo_test%auto%%", SourceCredentials{"asapo_test","auto","detector","","processed"},true,"auto beamline, source and no token"}, - {"processed%asapo_test%auto%%token", SourceCredentials{"asapo_test","auto","detector","token","processed"},true,"auto beamline, source"}, - {"processed%asapo_test%auto%source%", SourceCredentials{"asapo_test","auto","source","","processed"},true,"auto beamline, no token"}, - {"processed%asapo_test%auto%source%token", SourceCredentials{"asapo_test","auto","source","token","processed"},true,"auto beamline,source, token"}, - {"processed%asapo_test%beamline%source%token", SourceCredentials{"asapo_test","beamline","source","token","processed"},true,"all set"}, - {"processed%auto%beamline%source%token", SourceCredentials{"auto","beamline","source","token","processed"},true,"auto beamtime"}, - {"raw%auto%auto%source%token", SourceCredentials{},false,"auto beamtime and beamline"}, - {"raw%%beamline%source%token", SourceCredentials{"auto","beamline","source","token","raw"},true,"empty beamtime"}, - {"raw%asapo_test%%source%token", SourceCredentials{"asapo_test","auto","source","token","raw"},true,"empty bealine"}, - {"raw%%%source%token", SourceCredentials{},false,"both empty"}, - {"processed%asapo_test%beamline%source%blabla%token", SourceCredentials{"asapo_test","beamline","source%blabla","token","processed"},true,"% in source"}, - {"processed%asapo_test%beamline%source%blabla%", SourceCredentials{"asapo_test","beamline","source%blabla","","processed"},true,"% in source, no token"}, +}{ + {"processed%asapo_test%auto%%", SourceCredentials{"asapo_test", "auto", "detector", "", "processed"}, true, "auto beamline, source and no token"}, + {"processed%asapo_test%auto%%token", SourceCredentials{"asapo_test", "auto", "detector", "token", "processed"}, true, "auto beamline, source"}, + {"processed%asapo_test%auto%source%", SourceCredentials{"asapo_test", "auto", "source", "", "processed"}, true, "auto beamline, no token"}, + {"processed%asapo_test%auto%source%token", SourceCredentials{"asapo_test", "auto", "source", "token", "processed"}, true, "auto beamline,source, token"}, + {"processed%asapo_test%beamline%source%token", SourceCredentials{"asapo_test", "beamline", "source", "token", "processed"}, true, "all set"}, + {"processed%auto%beamline%source%token", SourceCredentials{"auto", "beamline", "source", "token", "processed"}, true, "auto beamtime"}, + {"raw%auto%auto%source%token", SourceCredentials{}, false, "auto beamtime and beamline"}, + {"raw%%beamline%source%token", SourceCredentials{"auto", "beamline", "source", "token", "raw"}, true, "empty beamtime"}, + {"raw%asapo_test%%source%token", SourceCredentials{"asapo_test", "auto", "source", "token", "raw"}, true, "empty bealine"}, + {"raw%%%source%token", SourceCredentials{}, false, "both empty"}, + {"processed%asapo_test%beamline%source%blabla%token", SourceCredentials{"asapo_test", "beamline", "source%blabla", "token", "processed"}, true, "% in source"}, + {"processed%asapo_test%beamline%source%blabla%", SourceCredentials{"asapo_test", "beamline", "source%blabla", "", "processed"}, true, "% in source, no token"}, } func TestSplitCreds(t *testing.T) { for _, test := range credTests { - request := authorizationRequest{test.request,"host"} - creds,err := getSourceCredentials(request) + request := authorizationRequest{test.request, "host"} + creds, err := getSourceCredentials(request) if test.ok { - assert.Nil(t,err) - assert.Equal(t,test.cred,creds,test.message) + assert.Nil(t, err) + assert.Equal(t, test.cred, creds, test.message) } else { - assert.NotNil(t,err,test.message) + assert.NotNil(t, err, test.message) } } } func TestAuthorizeDefaultOK(t *testing.T) { - allowBeamlines([]beamtimeMeta{{"asapo_test","beamline","","2019","tf","",nil}}) - request := makeRequest(authorizationRequest{"processed%asapo_test%%%","host"}) - w := doPostRequest("/authorize",request,"") + allowBeamlines([]common.BeamtimeMeta{{"asapo_test", "beamline", "", "2019", "tf", "", nil}}) + request := makeRequest(authorizationRequest{"processed%asapo_test%%%", "host"}) + w := doPostRequest("/authorize", request, "") body, _ := ioutil.ReadAll(w.Body) @@ -125,7 +126,7 @@ func TestAuthorizeDefaultOK(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code, "") } -var beamtime_meta_online =` +var beamtime_meta_online = ` { "beamline": "bl1", "beamtimeId": "test_online", @@ -133,7 +134,7 @@ var beamtime_meta_online =` } ` -var beamtime_meta =` +var beamtime_meta = ` { "applicant": { "email": "test", @@ -172,7 +173,7 @@ var beamtime_meta =` } ` -var commissioning_meta =` +var commissioning_meta = ` { "beamline": "P04", "corePath": "/asap3/petra3/gpfs/p04/2021/commissioning/c20210823_000_MAA", @@ -182,74 +183,82 @@ var commissioning_meta =` } ` - -var authTests = [] struct { +var authTests = []struct { source_type string beamtime_id string - beamline string - dataSource string - token string - originHost string - status int - message string - answer string + beamline string + dataSource string + token string + originHost string + status int + message string + answer string + mode int }{ - {"processed","test","auto","dataSource", prepareUserToken("bt_test",nil),"127.0.0.2",http.StatusUnauthorized,"missing access types", - ""}, - {"processed","test","auto","dataSource", prepareUserToken("bt_test",[]string{}),"127.0.0.2",http.StatusUnauthorized,"empty access types", - ""}, - {"processed","test","auto","dataSource", prepareUserToken("bt_test",[]string{"write"}),"127.0.0.2",http.StatusOK,"user source with correct token", - `{"beamtimeId":"test","beamline":"bl1","dataSource":"dataSource","corePath":"./tf/gpfs/bl1/2019/data/test","beamline-path":"","source-type":"processed","access-types":["write"]}`}, - {"processed","test_online","auto","dataSource", prepareUserToken("bt_test_online",[]string{"read"}),"127.0.0.1",http.StatusOK,"with online path, processed type", - `{"beamtimeId":"test_online","beamline":"bl1","dataSource":"dataSource","corePath":"./tf/gpfs/bl1/2019/data/test_online","beamline-path":"","source-type":"processed","access-types":["read"]}`}, - {"processed","test1","auto","dataSource", prepareUserToken("bt_test1",[]string{"read"}),"127.0.0.1",http.StatusUnauthorized,"correct token, beamtime not found", - ""}, - {"processed","test","auto","dataSource", prepareUserToken("wrong",[]string{"read"}),"127.0.0.1",http.StatusUnauthorized,"user source with wrong token", - ""}, - {"processed","test","bl1","dataSource", prepareUserToken("bt_test",[]string{"read"}),"127.0.0.1",http.StatusOK,"correct beamline given", - `{"beamtimeId":"test","beamline":"bl1","dataSource":"dataSource","corePath":"./tf/gpfs/bl1/2019/data/test","beamline-path":"","source-type":"processed","access-types":["read"]}`}, - {"processed","test","bl2","dataSource", prepareUserToken("bt_test",[]string{"read"}),"127.0.0.1",http.StatusUnauthorized,"incorrect beamline given", - ""}, - {"processed","auto","p07", "dataSource", prepareUserToken("bl_p07",[]string{"read"}),"127.0.0.1",http.StatusOK,"beamtime found", - `{"beamtimeId":"11111111","beamline":"p07","dataSource":"dataSource","corePath":"asap3/petra3/gpfs/p07/2020/data/11111111","beamline-path":"","source-type":"processed","access-types":["read"]}`}, - {"processed","auto","p07", "dataSource", prepareUserToken("bl_p06",[]string{"read"}),"127.0.0.1",http.StatusUnauthorized,"wrong token", - ""}, - {"processed","auto","p08", "dataSource", prepareUserToken("bl_p08",[]string{"read"}),"127.0.0.1",http.StatusUnauthorized,"beamtime not found", - ""}, - {"raw","test_online","auto","dataSource", "","127.0.0.1",http.StatusOK,"raw type", - `{"beamtimeId":"test_online","beamline":"bl1","dataSource":"dataSource","corePath":"./tf/gpfs/bl1/2019/data/test_online","beamline-path":"./bl1/current","source-type":"raw","access-types":["read","write"]}`}, - {"raw","test_online","auto","dataSource", "","127.0.0.1",http.StatusOK,"raw type", - `{"beamtimeId":"test_online","beamline":"bl1","dataSource":"dataSource","corePath":"./tf/gpfs/bl1/2019/data/test_online","beamline-path":"./bl1/current","source-type":"raw","access-types":["read","write"]}`}, - {"raw","auto","p07","dataSource", "","127.0.0.1",http.StatusOK,"raw type, auto beamtime", - `{"beamtimeId":"11111111","beamline":"p07","dataSource":"dataSource","corePath":"asap3/petra3/gpfs/p07/2020/data/11111111","beamline-path":"./p07/current","source-type":"raw","access-types":["read","write"]}`}, - {"raw","auto","p07","noldap", "","127.0.0.1",http.StatusNotFound,"no conection to ldap", - ""}, - {"raw","test_online","auto","dataSource", "","127.0.0.2",http.StatusUnauthorized,"raw type, wrong origin host", - ""}, - {"raw","test","auto","dataSource", prepareUserToken("bt_test",[]string{"read"}),"127.0.0.1",http.StatusUnauthorized,"raw when not online", - ""}, - {"processed","test","auto","dataSource", "","127.0.0.1:1001",http.StatusOK,"processed without token", - `{"beamtimeId":"test","beamline":"bl1","dataSource":"dataSource","corePath":"./tf/gpfs/bl1/2019/data/test","beamline-path":"","source-type":"processed","access-types":["read","write"]}`}, - {"processed","test","auto","dataSource", "","127.0.0.2",http.StatusUnauthorized,"processed without token, wrong host", - ""}, - {"raw","c20210823_000_MAA","auto","dataSource", "","127.0.0.1",http.StatusOK,"raw type commissioning", - `{"beamtimeId":"c20210823_000_MAA","beamline":"p04","dataSource":"dataSource","corePath":"./tf/gpfs/p04/2019/commissioning/c20210823_000_MAA","beamline-path":"./p04/commissioning","source-type":"raw","access-types":["read","write"]}`}, - {"processed","c20210823_000_MAA","auto","dataSource", "","127.0.0.1",http.StatusOK,"processed type commissioning", - `{"beamtimeId":"c20210823_000_MAA","beamline":"p04","dataSource":"dataSource","corePath":"./tf/gpfs/p04/2019/commissioning/c20210823_000_MAA","beamline-path":"","source-type":"processed","access-types":["read","write"]}`}, + {"processed", "test", "auto", "dataSource", prepareAsapoToken("bt_test", nil), "127.0.0.2", http.StatusUnauthorized, "missing access types", + "", 0}, + {"processed", "test", "auto", "dataSource", prepareAsapoToken("bt_test", []string{}), "127.0.0.2", http.StatusUnauthorized, "empty access types", + "", 0}, + {"processed", "test", "auto", "dataSource", prepareAsapoToken("bt_test", []string{"write"}), "127.0.0.2", http.StatusOK, "user source with correct token", + `{"beamtimeId":"test","beamline":"bl1","dataSource":"dataSource","corePath":"./tf/gpfs/bl1/2019/data/test","beamline-path":"","source-type":"processed","access-types":["write"]}`, 0}, + {"processed", "test", "auto", "dataSource", prepareAsapoToken("bt_test", []string{"write"}), "127.0.0.2", http.StatusUnauthorized, "token was revoked", + "", 2}, + {"processed", "test_online", "auto", "dataSource", prepareAsapoToken("bt_test_online", []string{"read"}), "127.0.0.1", http.StatusOK, "with online path, processed type", + `{"beamtimeId":"test_online","beamline":"bl1","dataSource":"dataSource","corePath":"./tf/gpfs/bl1/2019/data/test_online","beamline-path":"","source-type":"processed","access-types":["read"]}`, 0}, + {"processed", "test1", "auto", "dataSource", prepareAsapoToken("bt_test1", []string{"read"}), "127.0.0.1", http.StatusUnauthorized, "correct token, beamtime not found", + "", 1}, + {"processed", "test", "auto", "dataSource", prepareAsapoToken("wrong", []string{"read"}), "127.0.0.1", http.StatusUnauthorized, "user source with wrong token", + "", 0}, + {"processed", "test", "bl1", "dataSource", prepareAsapoToken("bt_test", []string{"read"}), "127.0.0.1", http.StatusOK, "correct beamline given", + `{"beamtimeId":"test","beamline":"bl1","dataSource":"dataSource","corePath":"./tf/gpfs/bl1/2019/data/test","beamline-path":"","source-type":"processed","access-types":["read"]}`, 0}, + {"processed", "test", "bl2", "dataSource", prepareAsapoToken("bt_test", []string{"read"}), "127.0.0.1", http.StatusUnauthorized, "incorrect beamline given", + "", 1}, + {"processed", "auto", "p07", "dataSource", prepareAsapoToken("bl_p07", []string{"read"}), "127.0.0.1", http.StatusOK, "beamtime found", + `{"beamtimeId":"11111111","beamline":"p07","dataSource":"dataSource","corePath":"asap3/petra3/gpfs/p07/2020/data/11111111","beamline-path":"","source-type":"processed","access-types":["read"]}`, 0}, + {"processed", "auto", "p07", "dataSource", prepareAsapoToken("bl_p06", []string{"read"}), "127.0.0.1", http.StatusUnauthorized, "wrong token", + "", 0}, + {"processed", "auto", "p08", "dataSource", prepareAsapoToken("bl_p08", []string{"read"}), "127.0.0.1", http.StatusUnauthorized, "beamtime not found", + "", 1}, + {"raw", "test_online", "auto", "dataSource", "", "127.0.0.1", http.StatusOK, "raw type", + `{"beamtimeId":"test_online","beamline":"bl1","dataSource":"dataSource","corePath":"./tf/gpfs/bl1/2019/data/test_online","beamline-path":"./bl1/current","source-type":"raw","access-types":["read","write","writeraw"]}`, 0}, + {"raw", "test_online", "auto", "dataSource", "", "127.0.0.1", http.StatusOK, "raw type", + `{"beamtimeId":"test_online","beamline":"bl1","dataSource":"dataSource","corePath":"./tf/gpfs/bl1/2019/data/test_online","beamline-path":"./bl1/current","source-type":"raw","access-types":["read","write","writeraw"]}`, 0}, + {"raw", "auto", "p07", "dataSource", "", "127.0.0.1", http.StatusOK, "raw type, auto beamtime", + `{"beamtimeId":"11111111","beamline":"p07","dataSource":"dataSource","corePath":"asap3/petra3/gpfs/p07/2020/data/11111111","beamline-path":"./p07/current","source-type":"raw","access-types":["read","write","writeraw"]}`, 0}, + {"raw", "auto", "p07", "noldap", "", "127.0.0.1", http.StatusServiceUnavailable, "no conection to ldap", + "", 0}, + + {"raw", "auto", "p07", "dataSource", prepareAsapoToken("bl_p07", []string{"read", "writeraw"}), "127.0.0.2", http.StatusOK, "raw type with token", + `{"beamtimeId":"11111111","beamline":"p07","dataSource":"dataSource","corePath":"asap3/petra3/gpfs/p07/2020/data/11111111","beamline-path":"./p07/current","source-type":"raw","access-types":["read","writeraw"]}`, 0}, + + {"raw", "test_online", "auto", "dataSource", "", "127.0.0.2", http.StatusUnauthorized, "raw type, wrong origin host", + "", 0}, + {"raw", "test", "auto", "dataSource", prepareAsapoToken("bt_test", []string{"read"}), "127.0.0.1", http.StatusUnauthorized, "raw when not online", + "", 1}, + {"processed", "test", "auto", "dataSource", "", "127.0.0.1:1001", http.StatusOK, "processed without token", + `{"beamtimeId":"test","beamline":"bl1","dataSource":"dataSource","corePath":"./tf/gpfs/bl1/2019/data/test","beamline-path":"","source-type":"processed","access-types":["read","write"]}`, 0}, + {"processed", "test", "auto", "dataSource", "", "127.0.0.2", http.StatusUnauthorized, "processed without token, wrong host", + "", 0}, + {"raw", "c20210823_000_MAA", "auto", "dataSource", "", "127.0.0.1", http.StatusOK, "raw type commissioning", + `{"beamtimeId":"c20210823_000_MAA","beamline":"p04","dataSource":"dataSource","corePath":"./tf/gpfs/p04/2019/commissioning/c20210823_000_MAA","beamline-path":"./p04/commissioning","source-type":"raw","access-types":["read","write","writeraw"]}`, 0}, + {"processed", "c20210823_000_MAA", "auto", "dataSource", "", "127.0.0.1", http.StatusOK, "processed type commissioning", + `{"beamtimeId":"c20210823_000_MAA","beamline":"p04","dataSource":"dataSource","corePath":"./tf/gpfs/p04/2019/commissioning/c20210823_000_MAA","beamline-path":"","source-type":"processed","access-types":["read","write"]}`, 0}, } func TestAuthorize(t *testing.T) { ldapClient = mockClient - allowBeamlines([]beamtimeMeta{}) - Auth = authorization.NewAuth(utils.NewJWTAuth("secret_user"),utils.NewJWTAuth("secret_admin"),utils.NewJWTAuth("secret")) + allowBeamlines([]common.BeamtimeMeta{}) + Auth = authorization.NewAuth(utils.NewJWTAuth("secret_user"), utils.NewJWTAuth("secret_admin"), utils.NewJWTAuth("secret")) + mock_store := new(token_store.MockedStore) + store = mock_store 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) @@ -262,109 +271,118 @@ func TestAuthorize(t *testing.T) { ioutil.WriteFile(filepath.Clean("bl1/current/beamtime-metadata-test_online.json"), []byte(beamtime_meta_online), 0644) ioutil.WriteFile(filepath.Clean("p04/commissioning/commissioning-metadata-c20210823_000_MAA.json"), []byte(commissioning_meta), 0644) - defer os.RemoveAll("p07") - defer os.RemoveAll("p04") - defer os.RemoveAll("tf") - defer os.RemoveAll("bl1") + defer os.RemoveAll("p07") + defer os.RemoveAll("p04") + defer os.RemoveAll("tf") + defer os.RemoveAll("bl1") for _, test := range authTests { - if test.source_type == "raw" || test.token == "" {bl := test.beamline + if test.token != "" && test.mode != 1 { + if test.mode == 2 { + mock_store.On("IsTokenRevoked", expectedTokenId).Return(true, nil) + } else { + mock_store.On("IsTokenRevoked", expectedTokenId).Return(false, nil) + } + } + + if test.source_type == "raw" || test.token == "" { + bl := test.beamline if test.beamline == "auto" { bl = "bl1" } - if iscommissioning(test.beamtime_id) && test.beamline == "auto" { + if iscommissioning(test.beamtime_id) && test.beamline == "auto" { bl = "p04" } - expected_filter:="a3"+bl+"-hosts" + expected_filter := "a3" + bl + "-hosts" if test.dataSource == "noldap" { - err := &common.ServerError{utils.StatusServiceUnavailable,""} - mockClient.On("GetAllowedIpsForBeamline", expected_uri, expected_base,expected_filter).Return([]string{}, err) + err := &common.ServerError{utils.StatusServiceUnavailable, ""} + mockClient.On("GetAllowedIpsForBeamline", expected_uri, expected_base, expected_filter).Return([]string{}, err) } else { - mockClient.On("GetAllowedIpsForBeamline", expected_uri, expected_base,expected_filter).Return(allowed_ips, nil) + mockClient.On("GetAllowedIpsForBeamline", expected_uri, expected_base, expected_filter).Return(allowed_ips, nil) } } - request := makeRequest(authorizationRequest{test.source_type+"%"+test.beamtime_id+"%"+test.beamline+"%"+test.dataSource+"%"+test.token,test.originHost}) - w := doPostRequest("/authorize",request,"") + request := makeRequest(authorizationRequest{test.source_type + "%" + test.beamtime_id + "%" + test.beamline + "%" + test.dataSource + "%" + test.token, test.originHost}) + w := doPostRequest("/authorize", request, "") body, _ := ioutil.ReadAll(w.Body) - if test.status==http.StatusOK { - body_str:=string(body) - body_str = strings.Replace(body_str,string(os.PathSeparator),"/",-1) - body_str = strings.Replace(body_str,"//","/",-1) - assert.Equal(t, test.answer,body_str,test.message) + if test.status == http.StatusOK { + body_str := string(body) + body_str = strings.Replace(body_str, string(os.PathSeparator), "/", -1) + body_str = strings.Replace(body_str, "//", "/", -1) + assert.Equal(t, test.answer, body_str, test.message) } - assert.Equal(t, test.status,w.Code, test.message) + assert.Equal(t, test.status, w.Code, test.message) mockClient.AssertExpectations(t) - mockClient.ExpectedCalls=nil + mockClient.ExpectedCalls = nil + mock_store.AssertExpectations(t) + mock_store.ExpectedCalls = nil + mock_store.Calls = nil } } func TestNotAuthorized(t *testing.T) { - request := makeRequest(authorizationRequest{"raw%any_id%%%","host"}) - w := doPostRequest("/authorize",request,"") + request := makeRequest(authorizationRequest{"raw%any_id%%%", "host"}) + w := doPostRequest("/authorize", request, "") assert.Equal(t, http.StatusUnauthorized, w.Code, "") } - func TestAuthorizeWrongRequest(t *testing.T) { - w := doPostRequest("/authorize","babla","") + w := doPostRequest("/authorize", "babla", "") assert.Equal(t, http.StatusBadRequest, w.Code, "") } - func TestAuthorizeWrongPath(t *testing.T) { - w := doPostRequest("/authorized","","") + w := doPostRequest("/authorized", "", "") assert.Equal(t, http.StatusNotFound, w.Code, "") } 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) - _,err := authorize(request,creds) - assert.Error(t,err, "") + request := authorizationRequest{"asapo_test%%", "host"} + creds, _ := getSourceCredentials(request) + _, err := authorize(request, creds) + assert.Error(t, err, "") } func TestSplitHost(t *testing.T) { host := splitHost("127.0.0.1:112") - assert.Equal(t,"127.0.0.1", host, "") + assert.Equal(t, "127.0.0.1", host, "") } - func TestSplitHostNoPort(t *testing.T) { host := splitHost("127.0.0.1") - assert.Equal(t,"127.0.0.1", host, "") + assert.Equal(t, "127.0.0.1", host, "") } -var extractBtinfoTests = [] struct { - root string - fname string +var extractBtinfoTests = []struct { + root string + fname string beamline string - id string - ok bool + id string + ok bool }{ - {".",filepath.Clean("tf/gpfs/bl1.01/2019/data/123"),"bl1.01","123",true}, - {filepath.Clean("/blabla/tratartra"),filepath.Clean("tf/gpfs/bl1.01/2019/data/123"), "bl1.01","123",true}, - {".",filepath.Clean("tf/gpfs/common/2019/data/123"), "bl1.01","123",false}, - {".",filepath.Clean("tf/gpfs/BeamtimeUsers/2019/data/123"), "bl1.01","123",false}, - {".",filepath.Clean("tf/gpfs/state/2019/data/123"), "bl1.01","123",false}, - {".",filepath.Clean("tf/gpfs/support/2019/data/123"), "bl1.01","123",false}, - {".",filepath.Clean("petra3/gpfs/p01/2019/commissioning/c20180508-000-COM20181"), "p01","c20180508-000-COM20181",true}, - + {".", filepath.Clean("tf/gpfs/bl1.01/2019/data/123"), "bl1.01", "123", true}, + {filepath.Clean("/blabla/tratartra"), filepath.Clean("tf/gpfs/bl1.01/2019/data/123"), "bl1.01", "123", true}, + {".", filepath.Clean("tf/gpfs/common/2019/data/123"), "bl1.01", "123", false}, + {".", filepath.Clean("tf/gpfs/BeamtimeUsers/2019/data/123"), "bl1.01", "123", false}, + {".", filepath.Clean("tf/gpfs/state/2019/data/123"), "bl1.01", "123", false}, + {".", filepath.Clean("tf/gpfs/support/2019/data/123"), "bl1.01", "123", false}, + {".", filepath.Clean("petra3/gpfs/p01/2019/commissioning/c20180508-000-COM20181"), "p01", "c20180508-000-COM20181", true}, } + func TestGetBeamtimeInfo(t *testing.T) { for _, test := range extractBtinfoTests { - settings.RootBeamtimesFolder=test.root - bt,err:= beamtimeMetaFromMatch(test.root+string(filepath.Separator)+test.fname) + 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) - assert.Equal(t,bt.Beamline,test.beamline) - assert.Equal(t,bt.BeamtimeId,test.id) - assert.Nil(t,err,"should not be error") + assert.Equal(t, bt.OfflinePath, test.root+string(filepath.Separator)+test.fname) + assert.Equal(t, bt.Beamline, test.beamline) + assert.Equal(t, bt.BeamtimeId, test.id) + assert.Nil(t, err, "should not be error") } else { - assert.NotNil(t,err,"should be error") + assert.NotNil(t, err, "should be error") } } 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 d5cf1f6c195904dbac3608cd1064d4ad5fc80a0d..ae39cee67b223f2d4d04da60cd749d240af877f7 100644 --- a/authorizer/src/asapo_authorizer/server/folder_token_test.go +++ b/authorizer/src/asapo_authorizer/server/folder_token_test.go @@ -2,16 +2,19 @@ package server import ( "asapo_authorizer/authorization" + "asapo_authorizer/common" + "asapo_authorizer/token_store" "asapo_common/structs" "asapo_common/utils" "asapo_common/version" + "fmt" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "io/ioutil" "net/http" - "testing" "os" "path/filepath" - "fmt" + "testing" ) var fodlerTokenTests = [] struct { @@ -23,23 +26,26 @@ var fodlerTokenTests = [] struct { status int message string }{ - {"test", false,"tf/gpfs/bl1/2019/data/test", "",prepareUserToken("bt_test",[]string{"read"}),http.StatusOK,"beamtime found"}, - {"test_online",false, "bl1/current", "",prepareUserToken("bt_test_online",[]string{"read"}),http.StatusOK,"online beamtime found"}, - {"test", false,"bl1/current", "",prepareUserToken("bt_test",[]string{"read"}),http.StatusUnauthorized,"no online beamtime found"}, - {"test_online",false, "bl2/current", "",prepareUserToken("bt_test_online",[]string{"read"}),http.StatusUnauthorized,"wrong online folder"}, - {"test", false,"tf/gpfs/bl1/2019/data/test1", "",prepareUserToken("bt_test",[]string{"read"}),http.StatusUnauthorized,"wrong folder"}, - {"test", false,"tf/gpfs/bl1/2019/data/test", "",prepareUserToken("bt_test1",[]string{"read"}),http.StatusUnauthorized,"wrong token"}, - {"11111111", false,"tf/gpfs/bl1/2019/data/test", "",prepareUserToken("bt_11111111",[]string{"read"}),http.StatusBadRequest,"bad request"}, + {"test", false,"tf/gpfs/bl1/2019/data/test", "", prepareAsapoToken("bt_test",[]string{"read"}),http.StatusOK,"beamtime found"}, + {"test_online",false, "bl1/current", "", prepareAsapoToken("bt_test_online",[]string{"read"}),http.StatusOK,"online beamtime found"}, + {"test", false,"bl1/current", "", prepareAsapoToken("bt_test",[]string{"read"}),http.StatusUnauthorized,"no online beamtime found"}, + {"test_online",false, "bl2/current", "", prepareAsapoToken("bt_test_online",[]string{"read"}),http.StatusUnauthorized,"wrong online folder"}, + {"test", false,"tf/gpfs/bl1/2019/data/test1", "", prepareAsapoToken("bt_test",[]string{"read"}),http.StatusUnauthorized,"wrong folder"}, + {"test", false,"tf/gpfs/bl1/2019/data/test", "", prepareAsapoToken("bt_test1",[]string{"read"}),http.StatusUnauthorized,"wrong token"}, + {"11111111", false,"tf/gpfs/bl1/2019/data/test", "", prepareAsapoToken("bt_11111111",[]string{"read"}),http.StatusBadRequest,"bad request"}, - {"test", true,"tf/gpfs/bl1/2019/data/test", "",prepareUserToken("bt_test",[]string{"read"}),http.StatusOK,"auto without onilne"}, - {"test_online",true, "tf/gpfs/bl1/2019/data/test_online", "bl1/current",prepareUserToken("bt_test_online",[]string{"read"}),http.StatusOK,"auto with online"}, + {"test", true,"tf/gpfs/bl1/2019/data/test", "", prepareAsapoToken("bt_test",[]string{"read"}),http.StatusOK,"auto without onilne"}, + {"test_online",true, "tf/gpfs/bl1/2019/data/test_online", "bl1/current", prepareAsapoToken("bt_test_online",[]string{"read"}),http.StatusOK,"auto with online"}, } func TestFolderToken(t *testing.T) { - allowBeamlines([]beamtimeMeta{}) - settings.RootBeamtimesFolder ="." - settings.CurrentBeamlinesFolder="." + allowBeamlines([]common.BeamtimeMeta{}) + mock_store := new(token_store.MockedStore) + store = mock_store + + 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 +58,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 { @@ -64,6 +70,8 @@ func TestFolderToken(t *testing.T) { request := makeRequest(folderTokenRequest{path_in_token,test.beamtime_id,test.token}) if test.status == http.StatusBadRequest { request =makeRequest(authorizationRequest{}) + } else { + mock_store.On("IsTokenRevoked", mock.Anything).Return(false, nil) } w := doPostRequest("/"+version.GetAuthorizerApiVersion()+"/folder",request,"") if w.Code == http.StatusOK { @@ -77,7 +85,9 @@ func TestFolderToken(t *testing.T) { body, _ := ioutil.ReadAll(w.Body) fmt.Println(string(body)) } - + mock_store.AssertExpectations(t) + mock_store.ExpectedCalls = nil + mock_store.Calls = nil assert.Equal(t, test.status, w.Code, test.message) } } diff --git a/authorizer/src/asapo_authorizer/server/introspect.go b/authorizer/src/asapo_authorizer/server/introspect.go index 1cc6bd37add60a6f8604ef0c47bc91b7eecb5345..5dd591a2c37f274e552cba63aab7e60829b5431b 100644 --- a/authorizer/src/asapo_authorizer/server/introspect.go +++ b/authorizer/src/asapo_authorizer/server/introspect.go @@ -19,10 +19,16 @@ func extractToken(r *http.Request) (string, error) { func verifyUserToken(token string) (response structs.IntrospectTokenResponse, err error) { var extra_claim structs.AccessTokenExtraClaim - response.Sub,err = Auth.UserAuth().CheckAndGetContent(token,&extra_claim) + claim,err := Auth.UserAuth().CheckAndGetContent(token,&extra_claim) if err!=nil { return } + err = checkTokenRevoked(claim.Id) + if err != nil { + return + } + + response.Sub = claim.Subject response.AccessTypes = extra_claim.AccessTypes return } diff --git a/authorizer/src/asapo_authorizer/server/introspect_test.go b/authorizer/src/asapo_authorizer/server/introspect_test.go index a9827bea06e6eba2c8d842def06037e86a9d896d..40a99b52fe531d720cb902feccc577202b472709 100644 --- a/authorizer/src/asapo_authorizer/server/introspect_test.go +++ b/authorizer/src/asapo_authorizer/server/introspect_test.go @@ -2,6 +2,7 @@ package server import ( "asapo_authorizer/authorization" + "asapo_authorizer/token_store" "asapo_common/structs" "asapo_common/utils" "encoding/json" @@ -27,11 +28,16 @@ func TestIntrospect(t *testing.T) { authJWT := utils.NewJWTAuth("secret") authAdmin := utils.NewJWTAuth("secret_admin") authUser := utils.NewJWTAuth("secret_user") + mock_store := new(token_store.MockedStore) + store = mock_store + Auth = authorization.NewAuth(authUser,authAdmin,authJWT) for _, test := range IntrospectTests { - token := prepareUserToken(test.tokenSubject,test.roles) + token := prepareAsapoToken(test.tokenSubject,test.roles) if test.status==http.StatusUnauthorized { token = "blabla" + } else { + mock_store.On("IsTokenRevoked", expectedTokenId).Return(false, nil) } request := makeRequest(structs.IntrospectTokenRequest{token}) w := doPostRequest("/introspect",request,"") @@ -46,6 +52,9 @@ func TestIntrospect(t *testing.T) { body, _ := ioutil.ReadAll(w.Body) fmt.Println(string(body)) } + mock_store.AssertExpectations(t) + mock_store.ExpectedCalls = nil + mock_store.Calls = nil } } diff --git a/authorizer/src/asapo_authorizer/server/issue_token.go b/authorizer/src/asapo_authorizer/server/issue_token.go index 7a332ad935d23c502514e98b440d9239afb4e560..bf2524abf9317081b2dd22f063cc1cf05d1774a9 100644 --- a/authorizer/src/asapo_authorizer/server/issue_token.go +++ b/authorizer/src/asapo_authorizer/server/issue_token.go @@ -2,11 +2,13 @@ package server import ( "asapo_authorizer/authorization" + "asapo_authorizer/token_store" log "asapo_common/logger" "asapo_common/structs" "asapo_common/utils" "errors" "net/http" + "time" ) func extractUserTokenrequest(r *http.Request) (request structs.IssueTokenRequest, err error) { @@ -28,7 +30,7 @@ func extractUserTokenrequest(r *http.Request) (request structs.IssueTokenRequest } for _, ar := range request.AccessTypes { - if ar != "read" && ar != "write" { + if ar != "read" && ar != "write" && !(ar== "writeraw" && request.Subject["beamline"]!="") { return request, errors.New("wrong requested access rights: "+ar) } } @@ -37,19 +39,12 @@ func extractUserTokenrequest(r *http.Request) (request structs.IssueTokenRequest } func routeAuthorisedTokenIssue(w http.ResponseWriter, r *http.Request) { - Auth.AdminAuth().ProcessAuth(checkAccessToken, "admin")(w, r) + Auth.AdminAuth().ProcessAuth(checkAccessToken, "")(w, r) } + func checkAccessToken(w http.ResponseWriter, r *http.Request) { - var extraClaim structs.AccessTokenExtraClaim - var claims *utils.CustomClaims - if err := utils.JobClaimFromContext(r, &claims, &extraClaim); err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - } - if claims.Subject != "admin" || !utils.StringInSlice("create",extraClaim.AccessTypes) { - err_txt := "wrong token claims" - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(err_txt)) + if checkRole(w, r, "create") != nil { + return } issueUserToken(w, r) @@ -62,12 +57,22 @@ func issueUserToken(w http.ResponseWriter, r *http.Request) { return } - token, err := Auth.PrepareAccessToken(request, true) + token, claims, err := Auth.PrepareAccessToken(request, true) if err != nil { utils.WriteServerError(w, err, http.StatusInternalServerError) return } + claims.StandardClaims.Issuer = "asapo-auth" + claims.StandardClaims.IssuedAt = time.Now().Unix() + record := token_store.TokenRecord{claims.Id, claims, token, false} + err = store.AddToken(record) + if err != nil { + utils.WriteServerError(w, err, http.StatusInternalServerError) + return + } + + log.Debug("generated user token ") answer := authorization.UserTokenResponce(request, token) diff --git a/authorizer/src/asapo_authorizer/server/issue_token_test.go b/authorizer/src/asapo_authorizer/server/issue_token_test.go index 5ed8c9b6a865449ecbda9f61f1c31183f3eb8d93..a36552063691c66572e98f276d1fd6550e2d7e00 100644 --- a/authorizer/src/asapo_authorizer/server/issue_token_test.go +++ b/authorizer/src/asapo_authorizer/server/issue_token_test.go @@ -2,11 +2,13 @@ package server import ( "asapo_authorizer/authorization" + "asapo_authorizer/token_store" "asapo_common/structs" "asapo_common/utils" "encoding/json" "fmt" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "io/ioutil" "net/http" "testing" @@ -24,7 +26,9 @@ var IssueTokenTests = [] struct { message string }{ {map[string]string{"beamtimeId":"test"},"bt_test",[]string{"read"},180,prepareAdminToken("admin"),"aaa",http.StatusOK,"read for beamtime"}, - {map[string]string{"beamtimeId":"test"},"bt_test",[]string{"read"},90,prepareAdminToken("admin"),"aaa",http.StatusOK,"write for beamtime"}, + {map[string]string{"beamtimeId":"test"},"bt_test",[]string{"write"},90,prepareAdminToken("admin"),"aaa",http.StatusOK,"write for beamtime"}, + {map[string]string{"beamtimeId":"test"},"bt_test",[]string{"writeraw"},90,prepareAdminToken("admin"),"",http.StatusBadRequest,"wrong role"}, + {map[string]string{"beamline":"test"},"bl_test",[]string{"writeraw"},90,prepareAdminToken("admin"),"aaa",http.StatusOK,"raw for beamline"}, {map[string]string{"beamline":"test"},"bl_test",[]string{"read"},180,prepareAdminToken("admin"),"aaa",http.StatusOK,"read for beamline"}, {map[string]string{"blabla":"test"},"",[]string{"read"},180,prepareAdminToken("admin"),"",http.StatusBadRequest,"beamline or beamtime not given"}, {map[string]string{"beamtimeId":"test"},"",[]string{"bla"},180,prepareAdminToken("admin"),"",http.StatusBadRequest,"wrong role"}, @@ -38,8 +42,15 @@ func TestIssueToken(t *testing.T) { authAdmin := utils.NewJWTAuth("secret_admin") authUser := utils.NewJWTAuth("secret_user") Auth = authorization.NewAuth(authUser,authAdmin,authJWT) + mock_store := new(token_store.MockedStore) + store = mock_store + for _, test := range IssueTokenTests { request := makeRequest(structs.IssueTokenRequest{test.requestSubject,test.validDays,test.roles}) + mock_store.On("IsTokenRevoked", mock.Anything).Return(false,nil) + if test.status == http.StatusOK { + mock_store.On("AddToken", mock.Anything).Return(nil) + } w := doPostRequest("/admin/issue",request,authAdmin.Name()+" "+test.adminToken) if w.Code == http.StatusOK { body, _ := ioutil.ReadAll(w.Body) @@ -58,6 +69,9 @@ func TestIssueToken(t *testing.T) { body, _ := ioutil.ReadAll(w.Body) fmt.Println(string(body)) } + mock_store.AssertExpectations(t) + mock_store.ExpectedCalls = nil + mock_store.Calls = nil assert.Equal(t, test.status, w.Code, test.message) } diff --git a/authorizer/src/asapo_authorizer/server/listroutes.go b/authorizer/src/asapo_authorizer/server/listroutes.go index 09b695091ffc4ecf686715a5b7a4f10434280cbb..d1f027f0aa048b2a0a3e41c9340694e99a11effb 100644 --- a/authorizer/src/asapo_authorizer/server/listroutes.go +++ b/authorizer/src/asapo_authorizer/server/listroutes.go @@ -17,6 +17,12 @@ var listRoutes = utils.Routes{ "/introspect", routeIntrospect, }, + utils.Route{ + "Authorize", + "POST", + "/admin/revoke", + routeRevoke, + }, utils.Route{ "HealthCheck", "Get", diff --git a/authorizer/src/asapo_authorizer/server/revoke_token.go b/authorizer/src/asapo_authorizer/server/revoke_token.go new file mode 100644 index 0000000000000000000000000000000000000000..c1c98c6f2adf4c3a3fe05fd94b5773352861b75c --- /dev/null +++ b/authorizer/src/asapo_authorizer/server/revoke_token.go @@ -0,0 +1,39 @@ +package server + +import ( + log "asapo_common/logger" + "asapo_common/utils" + "encoding/json" + "net/http" +) + +func routeRevoke(w http.ResponseWriter, r *http.Request) { + Auth.AdminAuth().ProcessAuth(processRevoke, "")(w, r) +} + +func processRevoke(w http.ResponseWriter, r *http.Request) { + if checkRole(w, r, "revoke") != nil { + return + } + revokeToken(w, r) +} + +func revokeToken(w http.ResponseWriter, r *http.Request) { + token, err := extractToken(r) + if err != nil { + utils.WriteServerError(w, err, http.StatusBadRequest) + return + } + + rec, err := store.RevokeToken(token, "") + if err != nil { + log.Error("could not revoke token "+ token+": "+ err.Error()) + utils.WriteServerError(w, err, http.StatusBadRequest) + return + } + + log.Debug("revoked token " + rec.Token) + answer, _ := json.Marshal(&rec) + w.WriteHeader(http.StatusOK) + w.Write(answer) +} diff --git a/authorizer/src/asapo_authorizer/server/server.go b/authorizer/src/asapo_authorizer/server/server.go index 59d43987d7aafface0687500409b7efdcf0ee2be..8a854967d01912370a7e133612bf2ab6c5a76cce 100644 --- a/authorizer/src/asapo_authorizer/server/server.go +++ b/authorizer/src/asapo_authorizer/server/server.go @@ -2,43 +2,16 @@ package server import ( "asapo_authorizer/authorization" + "asapo_authorizer/common" "asapo_authorizer/ldap_client" + "asapo_authorizer/token_store" + "sync" ) -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 - } -} - -var settings serverSettings var ldapClient ldap_client.LdapClient var Auth *authorization.Auth - +var store token_store.Store +var cachedMetas = struct { + cache map[string]common.BeamtimeMeta + lock sync.Mutex +}{cache: make(map[string]common.BeamtimeMeta, 0)} diff --git a/authorizer/src/asapo_authorizer/server/server_nottested.go b/authorizer/src/asapo_authorizer/server/server_nottested.go index 2f428370f6955303d582f7bd22a551cb4dace88c..6841cf5f377ddbbbcbd2c91aa3c4710ce6540bbf 100644 --- a/authorizer/src/asapo_authorizer/server/server_nottested.go +++ b/authorizer/src/asapo_authorizer/server/server_nottested.go @@ -4,29 +4,42 @@ package server import ( "asapo_authorizer/authorization" + "asapo_authorizer/common" "asapo_authorizer/ldap_client" + "asapo_authorizer/token_store" log "asapo_common/logger" "asapo_common/utils" "asapo_common/version" "errors" "net/http" + _ "net/http/pprof" "strconv" ) func Start() { mux := utils.NewRouter(listRoutes) ldapClient = new(ldap_client.OpenLdapClient) + log.Info("Starting ASAPO Authorizer, version " + version.GetVersion()) - log.Info("Listening on port: " + strconv.Itoa(settings.Port)) - log.Fatal(http.ListenAndServe(":"+strconv.Itoa(settings.Port), http.HandlerFunc(mux.ServeHTTP))) + + store = new(token_store.TokenStore) + err := store.Init(nil) + if err != nil { + log.Error(err.Error()) + } + defer store.Close() + + log.Info("Listening on port: " + strconv.Itoa(common.Settings.Port)) + mux.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux) + 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 } @@ -34,29 +47,37 @@ 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 common.Settings.DatabaseServer == "" { + return log.FatalLevel, errors.New("Database server not set") + } + + if common.Settings.DatabaseServer == "auto" && common.Settings.DiscoveryServer=="" { + return log.FatalLevel, errors.New("Discovery server not set") + } + var err error Auth, err = createAuth() if err != nil { 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 new file mode 100644 index 0000000000000000000000000000000000000000..f18a0f738e4b4ed9397f8449a679f8650b7f6928 --- /dev/null +++ b/authorizer/src/asapo_authorizer/server/server_test.go @@ -0,0 +1,143 @@ +package server + +/* + +import ( + "asapo_authorizer/database" + "asapo_common/discovery" + "asapo_common/logger" + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "net/http" + "net/http/httptest" + "testing" +) + +func setup() *database.MockedDatabase { + mock_db := new(database.MockedDatabase) + mock_db.On("Connect", mock.AnythingOfType("string")).Return(nil) + + return mock_db +} + +func setup_and_init(t *testing.T) *database.MockedDatabase { + mock_db := new(database.MockedDatabase) + mock_db.On("Connect", mock.AnythingOfType("string")).Return(nil) + + InitDB(mock_db) + assertExpectations(t, mock_db) + return mock_db +} + +func assertExpectations(t *testing.T, mock_db *database.MockedDatabase) { + mock_db.AssertExpectations(t) + mock_db.ExpectedCalls = nil + logger.MockLog.AssertExpectations(t) + logger.MockLog.ExpectedCalls = nil +} + +var initDBTests = []struct { + address string + answer error + message string +}{ + {"bad address", errors.New(""), "error on get bad address"}, + {"good address", nil, "no error on good address"}, +} + +func TestInitDBWithWrongAddress(t *testing.T) { + mock_db := setup() + + mock_db.ExpectedCalls = nil + + settings.DatabaseServer = "0.0.0.0:0000" + + for _, test := range initDBTests { + mock_db.On("Connect", "0.0.0.0:0000").Return(test.answer) + + err := InitDB(mock_db) + + assert.Equal(t, test.answer, err, test.message) + assertExpectations(t, mock_db) + } + db = nil +} + +func TestInitDBWithAutoAddress(t *testing.T) { + mongo_address := "0.0.0.0:0000" + mock_db := setup() + + mock_db.ExpectedCalls = nil + + settings.DatabaseServer = "auto" + mock_server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + assert.Equal(t, req.URL.String(), "/asapo-mongodb", "request string") + rw.Write([]byte(mongo_address)) + })) + defer mock_server.Close() + + discoveryService = discovery.CreateDiscoveryService(mock_server.Client(), mock_server.URL) + + mock_db.On("Connect", "0.0.0.0:0000").Return(nil) + + err := InitDB(mock_db) + + assert.Equal(t, nil, err, "auto connect ok") + assertExpectations(t, mock_db) + db = nil +} + +func TestReconnectDB(t *testing.T) { + mongo_address := "0.0.0.0:0000" + mock_server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + assert.Equal(t, req.URL.String(), "/asapo-mongodb", "request string") + rw.Write([]byte(mongo_address)) + })) + discoveryService = discovery.CreateDiscoveryService(mock_server.Client(), mock_server.URL) + + defer mock_server.Close() + + settings.DatabaseServer = "auto" + mock_db := setup_and_init(t) + mock_db.ExpectedCalls = nil + + mongo_address = "1.0.0.0:0000" + + mock_db.On("Close").Return() + + mock_db.On("Connect", "1.0.0.0:0000").Return(nil) + + err := ReconnectDb() + assert.Equal(t, nil, err, "auto connect ok") + assertExpectations(t, mock_db) + + db = nil +} + +func TestErrorWhenReconnectNotConnectedDB(t *testing.T) { + err := ReconnectDb() + assert.NotNil(t, err, "error reconnect") + db = nil +} + + +func TestCleanupDBWithoutInit(t *testing.T) { + mock_db := setup() + + mock_db.AssertNotCalled(t, "Close") + + CleanupDB() +} + +func TestCleanupDBInit(t *testing.T) { + settings.DatabaseServer = "0.0.0.0" + mock_db := setup_and_init(t) + + mock_db.On("Close").Return() + + CleanupDB() + + assertExpectations(t, mock_db) +} +*/ \ No newline at end of file diff --git a/authorizer/src/asapo_authorizer/token_store/database.go b/authorizer/src/asapo_authorizer/token_store/database.go new file mode 100644 index 0000000000000000000000000000000000000000..2afabbe9ffe0fc058e3da879bb751d466d268042 --- /dev/null +++ b/authorizer/src/asapo_authorizer/token_store/database.go @@ -0,0 +1,50 @@ +package token_store + +import "asapo_common/utils" + + +const KAdminDb = "asapo_admin" +const KTokens = "tokens" +const KRevokedTokens = "revoked_tokens" + +type Request struct { + DbName string + Collection string + Op string +} + +type TokenRecord struct { + Id string `bson:"_id"` + *utils.CustomClaims + Token string + Revoked bool +} + +type IdRecord struct { + Id string `bson:"_id"` +} + +type Agent interface { + ProcessRequest(request Request, extraParams ...interface{}) ([]byte, error) + Ping() error + Connect(string) error + Close() +} + +type DBError struct { + Code int + Message string +} + +func (err *DBError) Error() string { + return err.Message +} + +func GetStatusCodeFromError(err error) int { + err_db, ok := err.(*DBError) + if ok { + return err_db.Code + } else { + return utils.StatusServiceUnavailable + } +} diff --git a/authorizer/src/asapo_authorizer/token_store/mock_database.go b/authorizer/src/asapo_authorizer/token_store/mock_database.go new file mode 100644 index 0000000000000000000000000000000000000000..7878da8d43baecbb694a9d880a5c7db972444a2b --- /dev/null +++ b/authorizer/src/asapo_authorizer/token_store/mock_database.go @@ -0,0 +1,50 @@ +// +build !release + +package token_store + +import ( + "github.com/stretchr/testify/mock" +) + +type MockedDatabase struct { + mock.Mock +} + +func (db *MockedDatabase) Connect(address string) error { + args := db.Called(address) + return args.Error(0) +} + +func (db *MockedDatabase) Close() { + db.Called() +} + +func (db *MockedDatabase) Ping() error { + args := db.Called() + return args.Error(0) +} + +func (db *MockedDatabase) ProcessRequest(request Request, extraParams ...interface{}) (answer []byte, err error) { + 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..7d0a35923d098701ad1fb329de1c2d85db944b70 --- /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) IsTokenRevoked(tokenId string) (bool, error) { + args := store.Called(tokenId) + return args.Get(0).(bool), args.Error(1) +} + +func (store *MockedStore) Close() { + store.Called() + return +} + + diff --git a/authorizer/src/asapo_authorizer/token_store/mongodb.go b/authorizer/src/asapo_authorizer/token_store/mongodb.go new file mode 100644 index 0000000000000000000000000000000000000000..fbd1359125d9ad023f7074a909280b3e5a1b946b --- /dev/null +++ b/authorizer/src/asapo_authorizer/token_store/mongodb.go @@ -0,0 +1,210 @@ +//+build !test + +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" + "strings" + "sync" + "time" +) + +const no_session_msg = "database client not created" +const already_connected_msg = "already connected" + +var dbSessionLock sync.Mutex +var dbClientLock sync.RWMutex + +type Mongodb struct { + client *mongo.Client + timeout time.Duration + lastReadFromInprocess map[string]int64 +} + +func (db *Mongodb) Ping() (err error) { + if db.client == nil { + return &DBError{utils.StatusServiceUnavailable, no_session_msg} + } + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + return db.client.Ping(ctx, nil) +} + +func (db *Mongodb) Connect(address string) (err error) { + dbClientLock.Lock() + defer dbClientLock.Unlock() + + if db.client != nil { + return &DBError{utils.StatusServiceUnavailable, already_connected_msg} + } + db.client, err = mongo.NewClient(options.Client().SetConnectTimeout(20 * time.Second).ApplyURI("mongodb://" + address)) + if err != nil { + return err + } + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + err = db.client.Connect(ctx) + if err != nil { + db.client = nil + return err + } + + if db.lastReadFromInprocess == nil { + db.lastReadFromInprocess = make(map[string]int64, 100) + } + + return db.Ping() +} + +func (db *Mongodb) Close() { + dbClientLock.Lock() + defer dbClientLock.Unlock() + if db.client != nil { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + db.client.Disconnect(ctx) + db.client = nil + } +} + +func (db *Mongodb) insertRecord(dbname string, collection_name string, s interface{}) error { + if db.client == nil { + return &DBError{utils.StatusServiceUnavailable, no_session_msg} + } + + c := db.client.Database(dbname).Collection(collection_name) + + _, err := c.InsertOne(context.TODO(), s) + return err +} + +func duplicateError(err error) bool { + command_error, ok := err.(mongo.CommandError) + if !ok { + write_exception_error, ok1 := err.(mongo.WriteException) + if !ok1 { + return false + } + return strings.Contains(write_exception_error.Error(), "duplicate key") + } + return command_error.Name == "DuplicateKey" +} + +func (db *Mongodb) checkDatabaseOperationPrerequisites(request Request) error { + if db.client == nil { + return &DBError{utils.StatusServiceUnavailable, no_session_msg} + } + + if len(request.DbName) == 0 || len(request.Collection) == 0 { + return &DBError{utils.StatusWrongInput, "database ans collection must be set"} + } + + return nil +} + +func (db *Mongodb) createRecord(request Request, extra_params ...interface{}) ([]byte, error) { + if len(extra_params) != 1 { + return nil, errors.New("wrong number of parameters") + } + record := extra_params[0] + c := db.client.Database(request.DbName).Collection(request.Collection) + res, err := c.InsertOne(context.TODO(), record, options.InsertOne()) + if err != nil { + return nil, err + } + newId, ok := res.InsertedID.(primitive.ObjectID) + if ok { + return []byte(newId.Hex()), nil + } + 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) { + dbClientLock.RLock() + defer dbClientLock.RUnlock() + + if err := db.checkDatabaseOperationPrerequisites(request); err != nil { + return nil, err + } + + 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..756d7cc2d970292697c0b2fd4ecd0fcebcd909a9 --- /dev/null +++ b/authorizer/src/asapo_authorizer/token_store/token_store.go @@ -0,0 +1,246 @@ +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) + IsTokenRevoked(tokenId string) (bool, 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) + 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) + } + + store.revokedTokenList.lock.Lock() + defer store.revokedTokenList.lock.Unlock() + store.revokedTokenList.tokens = append(store.revokedTokenList.tokens, tokenRecord.Id) + 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.revokedTokenList.tokens = nil + 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.lastError = nil + 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) IsTokenRevoked(tokenId string) (bool, error) { + tokens, err := store.getRevokedTokenIds() + if err != nil { + return true, err + } + return utils.StringInSlice(tokenId, tokens), nil +} + +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..d648f0ef3afbcf9d259237f425a3ca7ccc85e449 --- /dev/null +++ b/authorizer/src/asapo_authorizer/token_store/token_store_test.go @@ -0,0 +1,147 @@ +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) TestProcessRequestCheckRevokedToken() { + 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) + res,err := suite.store.IsTokenRevoked("123") + suite.Equal(err, nil, "ok") + suite.Equal(false, res, "ok") +} \ No newline at end of file diff --git a/broker/src/asapo_broker/database/mongodb.go b/broker/src/asapo_broker/database/mongodb.go index d248f016fd2cd719da9dc8c0b86f4c1803142e8d..37eb03008fd1ab8df9b4f8c187e5542459ce5c11 100644 --- a/broker/src/asapo_broker/database/mongodb.go +++ b/broker/src/asapo_broker/database/mongodb.go @@ -75,6 +75,7 @@ const stream_filter_finished = "finished" const stream_filter_unfinished = "unfinished" var dbSessionLock sync.Mutex +var dbClientLock sync.RWMutex type SizeRecord struct { Size int `bson:"size" json:"size"` @@ -101,8 +102,8 @@ func (db *Mongodb) Ping() (err error) { } func (db *Mongodb) Connect(address string) (err error) { - dbSessionLock.Lock() - defer dbSessionLock.Unlock() + dbClientLock.Lock() + defer dbClientLock.Unlock() if db.client != nil { return &DBError{utils.StatusServiceUnavailable, already_connected_msg} @@ -127,8 +128,8 @@ func (db *Mongodb) Connect(address string) (err error) { } func (db *Mongodb) Close() { - dbSessionLock.Lock() - defer dbSessionLock.Unlock() + dbClientLock.Lock() + defer dbClientLock.Unlock() if db.client != nil { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -672,7 +673,7 @@ func (db *Mongodb) resetCounter(request Request) ([]byte, error) { c := db.client.Database(request.DbName).Collection(inprocess_collection_name_prefix + request.Stream + "_" + request.GroupId) _, err_del := c.DeleteMany(context.Background(), bson.M{"_id": bson.M{"$gte": id}}) if err_del != nil { - return nil, &DBError{utils.StatusWrongInput, err.Error()} + return nil, &DBError{utils.StatusWrongInput, err_del.Error()} } return []byte(""), nil @@ -1021,6 +1022,9 @@ func (db *Mongodb) getStreams(request Request) ([]byte, error) { } func (db *Mongodb) ProcessRequest(request Request) (answer []byte, err error) { + dbClientLock.RLock() + defer dbClientLock.RUnlock() + if err := db.checkDatabaseOperationPrerequisites(request); err != nil { return nil, err } diff --git a/broker/src/asapo_broker/database/mongodb_streams.go b/broker/src/asapo_broker/database/mongodb_streams.go index 243df816d1e182c2dd767e5dc0db106a57ee40d8..a182f5080409c00116af1958d2b65dcc49983e75 100644 --- a/broker/src/asapo_broker/database/mongodb_streams.go +++ b/broker/src/asapo_broker/database/mongodb_streams.go @@ -190,16 +190,8 @@ func (ss *Streams) updateFromDb(db *Mongodb, db_name string) (StreamsRecord, err } func getFiltersFromString(filterString string) (string, string, error) { - firstStream := "" - streamStatus := "" - s := strings.Split(filterString, "_") - switch len(s) { - case 1: - firstStream = s[0] - case 2: - firstStream = s[0] - streamStatus = s[1] - default: + firstStream, streamStatus, err := utils.DecodeTwoStrings(filterString) + if err!=nil { return "", "", errors.New("wrong format: " + filterString) } if streamStatus == "" { diff --git a/broker/src/asapo_broker/database/mongodb_test.go b/broker/src/asapo_broker/database/mongodb_test.go index e4c8f458d43b84ae615dfd8e0b0ef4a1435c53f2..c81813611b03dbed3b7124ca1fa8227ce88ca897 100644 --- a/broker/src/asapo_broker/database/mongodb_test.go +++ b/broker/src/asapo_broker/database/mongodb_test.go @@ -930,7 +930,7 @@ func TestMongoDBListStreams(t *testing.T) { } var rec_streams_expect, _ = json.Marshal(test.expectedStreams) - res, err := db.ProcessRequest(Request{DbName: dbname, Stream: "0", Op: "streams", ExtraParam: test.from}) + res, err := db.ProcessRequest(Request{DbName: dbname, Stream: "0", Op: "streams", ExtraParam: utils.EncodeTwoStrings(test.from,"")}) if test.ok { assert.Nil(t, err, test.test) assert.Equal(t, string(rec_streams_expect), string(res), test.test) diff --git a/broker/src/asapo_broker/database/streams_test.go b/broker/src/asapo_broker/database/streams_test.go index fef6ff3afb5d5f5f74bcf85975c5e22748d2388e..4ba11e0b3986ff93ea26289054a11f573d670e5c 100644 --- a/broker/src/asapo_broker/database/streams_test.go +++ b/broker/src/asapo_broker/database/streams_test.go @@ -3,6 +3,7 @@ package database import ( + "asapo_common/utils" "fmt" "github.com/stretchr/testify/suite" "testing" @@ -105,8 +106,8 @@ func (suite *StreamsTestSuite) TestStreamsMultipleRequests() { db.insertRecord(dbname, collection, &rec_dataset1_incomplete) db.insertRecord(dbname, collection, &rec_finished) db.insertRecord(dbname, collection2, &rec_dataset1_incomplete) - rec, err := streams.getStreams(&db, Request{DbName: dbname, ExtraParam: "_unfinished"}) - rec2, err2 := streams.getStreams(&db, Request{DbName: dbname, ExtraParam: "_finished"}) + rec, err := streams.getStreams(&db, Request{DbName: dbname, ExtraParam: "0/unfinished"}) + rec2, err2 := streams.getStreams(&db, Request{DbName: dbname, ExtraParam: "0/finished"}) suite.Nil(err) suite.Equal(collection2, rec.Streams[0].Name) suite.Equal(1, len(rec.Streams)) @@ -143,17 +144,17 @@ var streamFilterTests=[]struct{ message string }{ {request: Request{DbName:dbname, ExtraParam:""},error: false,streams: []string{collection,collection2},message: "default all streams"}, - {request: Request{DbName:dbname, ExtraParam:"_"},error: false,streams: []string{collection,collection2},message: "default _ all streams"}, - {request: Request{DbName:dbname, ExtraParam:collection},error: false,streams: []string{collection,collection2},message: "first parameter only - all streams"}, - {request: Request{DbName:dbname, ExtraParam:"_all"},error: false,streams: []string{collection,collection2},message: "second parameter only - all streams"}, - {request: Request{DbName:dbname, ExtraParam:"_finished"},error: false,streams: []string{collection2},message: "second parameter only - finished streams"}, - {request: Request{DbName:dbname, ExtraParam:"_unfinished"},error: false,streams: []string{collection},message: "second parameter only - unfinished streams"}, - {request: Request{DbName:dbname, ExtraParam:collection2+"_all"},error: false,streams: []string{collection2},message: "from stream2"}, - {request: Request{DbName:dbname, ExtraParam:collection2+"_unfinished"},error: false,streams: []string{},message: "from stream2 and filter"}, - {request: Request{DbName:dbname, ExtraParam:collection2+"_bla"},error: true,streams: []string{},message: "wrong filter"}, - {request: Request{DbName:dbname, ExtraParam:collection2+"_all_aaa"},error: true,streams: []string{},message: "wrong filter2"}, - {request: Request{DbName:dbname, ExtraParam:"blabla"},error: false,streams: []string{},message: "from unknown stream returns nothing"}, - {request: Request{DbName:dbname, ExtraParam:collection2+"_"},error: false,streams: []string{collection2},message: "from stream2, first parameter only"}, + {request: Request{DbName:dbname, ExtraParam:"0/"},error: false,streams: []string{collection,collection2},message: "default 0/ all streams"}, + {request: Request{DbName:dbname, ExtraParam:utils.EncodeTwoStrings(collection,"")},error: false,streams: []string{collection,collection2},message: "first parameter only - all streams"}, + {request: Request{DbName:dbname, ExtraParam:"0/all"},error: false,streams: []string{collection,collection2},message: "second parameter only - all streams"}, + {request: Request{DbName:dbname, ExtraParam:"0/finished"},error: false,streams: []string{collection2},message: "second parameter only - finished streams"}, + {request: Request{DbName:dbname, ExtraParam:"0/unfinished"},error: false,streams: []string{collection},message: "second parameter only - unfinished streams"}, + {request: Request{DbName:dbname, ExtraParam:utils.EncodeTwoStrings(collection2,"all")},error: false,streams: []string{collection2},message: "from stream2"}, + {request: Request{DbName:dbname, ExtraParam:utils.EncodeTwoStrings(collection2,"unfinished")},error: false,streams: []string{},message: "from stream2 and filter"}, + {request: Request{DbName:dbname, ExtraParam:utils.EncodeTwoStrings(collection2,"bla")},error: true,streams: []string{},message: "wrong filter"}, + {request: Request{DbName:dbname, ExtraParam:utils.EncodeTwoStrings(collection2,"all_aaa")},error: true,streams: []string{},message: "wrong filter2"}, + {request: Request{DbName:dbname, ExtraParam:utils.EncodeTwoStrings("blabla","")},error: false,streams: []string{},message: "from unknown stream returns nothing"}, + {request: Request{DbName:dbname, ExtraParam:utils.EncodeTwoStrings(collection2,"")},error: false,streams: []string{collection2},message: "from stream2, first parameter only"}, } func (suite *StreamsTestSuite) TestStreamFilters() { diff --git a/broker/src/asapo_broker/server/authorizer.go b/broker/src/asapo_broker/server/authorizer.go index 66a01d31a8ac1866082ef8b4ce4810090ce59126..cac330d1be034930207613d9cd15c69b368a1153 100644 --- a/broker/src/asapo_broker/server/authorizer.go +++ b/broker/src/asapo_broker/server/authorizer.go @@ -23,8 +23,8 @@ type HttpClient interface { Do(req *http.Request) (*http.Response, error) } -type AuthorizationError struct{ - err error +type AuthorizationError struct { + err error statusCode int } @@ -33,34 +33,34 @@ func (m AuthorizationError) Error() string { } type AsapoAuthorizer struct { - serverUrl string + serverUrl string httpClient HttpClient } -type cachedToken struct{ +type cachedToken struct { Token lastUpdate time.Time } -var cachedTokens = struct { - tokens map[string]cachedToken +var cachedTokens = struct { + tokens map[string]cachedToken cachedTokensLock sync.RWMutex -}{tokens:make(map[string]cachedToken,0)} +}{tokens: make(map[string]cachedToken, 0)} -func getCachedToken(tokenJWT string)(token Token, ok bool) { +func getCachedToken(tokenJWT string) (token Token, ok bool) { cachedTokens.cachedTokensLock.RLock() - defer cachedTokens.cachedTokensLock.RUnlock() - cachedToken,ok:=cachedTokens.tokens[tokenJWT] - if !ok{ - return token,false + cachedToken, ok := cachedTokens.tokens[tokenJWT] + cachedTokens.cachedTokensLock.RUnlock() + if !ok { + return token, false } - if time.Now().Sub(cachedToken.lastUpdate) < 10000*time.Second { + if time.Now().Sub(cachedToken.lastUpdate) < time.Duration(settings.GetTokenCacheUpdateInterval())*time.Millisecond { return cachedToken.Token, true } - return token,false + return token, false } -func cacheToken(tokenJWT string,token Token) { +func cacheToken(tokenJWT string, token Token) { cachedTokens.cachedTokensLock.Lock() defer cachedTokens.cachedTokensLock.Unlock() @@ -70,7 +70,7 @@ func cacheToken(tokenJWT string,token Token) { } } -func (a * AsapoAuthorizer) doRequest(req *http.Request) (token Token, err error) { +func (a *AsapoAuthorizer) doRequest(req *http.Request) (token Token, err error) { resp, err := a.httpClient.Do(req) if err != nil { return token, err @@ -85,8 +85,10 @@ func (a * AsapoAuthorizer) doRequest(req *http.Request) (token Token, err error) switch resp.StatusCode { case http.StatusOK: //do nothing - case http.StatusUnauthorized: - return token, &AuthorizationError{errors.New("authorizer rejected to authorize: " + string(body)),http.StatusUnauthorized} + case http.StatusUnauthorized: + return token, &AuthorizationError{errors.New("authorizer rejected to authorize: " + string(body)), http.StatusUnauthorized} + case http.StatusServiceUnavailable: + return token, &AuthorizationError{errors.New("authorizer service unavailable: " + string(body)), http.StatusServiceUnavailable} default: return token, errors.New("authorizer returned " + resp.Status + ": " + string(body)) } @@ -96,7 +98,7 @@ func (a * AsapoAuthorizer) doRequest(req *http.Request) (token Token, err error) } func createIntrospectTokenRequest(tokenJWT string) (*http.Request, error) { - path := "http://"+settings.AuthorizationServer + "/introspect" + path := "http://" + settings.AuthorizationServer + "/introspect" request := struct { Token string }{tokenJWT} @@ -109,8 +111,8 @@ func createIntrospectTokenRequest(tokenJWT string) (*http.Request, error) { return req, nil } -func (a * AsapoAuthorizer) AuthorizeToken(tokenJWT string) (token Token, err error) { - token,ok := getCachedToken(tokenJWT) +func (a *AsapoAuthorizer) AuthorizeToken(tokenJWT string) (token Token, err error) { + token, ok := getCachedToken(tokenJWT) if ok { return } @@ -127,4 +129,3 @@ func (a * AsapoAuthorizer) AuthorizeToken(tokenJWT string) (token Token, err err return } - diff --git a/broker/src/asapo_broker/server/get_commands_test.go b/broker/src/asapo_broker/server/get_commands_test.go index 980946e49cd6022dc1c781fd8e3098729a6778f3..3f34750b20451d69d8fc62f456070e905f1327b7 100644 --- a/broker/src/asapo_broker/server/get_commands_test.go +++ b/broker/src/asapo_broker/server/get_commands_test.go @@ -52,7 +52,7 @@ var testsGetCommand = []struct { expectedGroupID + "/next","&resend_nacks=true&delay_ms=10000&resend_attempts=3","10000_3"}, {"size", expectedSource,expectedStream, "", expectedStream + "/size","",""}, {"size",expectedSource, expectedStream, "", expectedStream + "/size","&incomplete=true","true"}, - {"streams",expectedSource, "0", "", "0/streams","","_"}, + {"streams",expectedSource, "0", "", "0/streams","","0/"}, {"lastack", expectedSource,expectedStream, expectedGroupID, expectedStream + "/" + expectedGroupID + "/lastack","",""}, } diff --git a/broker/src/asapo_broker/server/get_streams.go b/broker/src/asapo_broker/server/get_streams.go index a22274553f58663c2bdbd830c246344b48f0dea9..01e1e8edc57e920fecee4d2b9560f5087363ca3f 100644 --- a/broker/src/asapo_broker/server/get_streams.go +++ b/broker/src/asapo_broker/server/get_streams.go @@ -1,6 +1,7 @@ package server import ( + "asapo_common/utils" "net/http" ) @@ -8,5 +9,7 @@ func routeGetStreams(w http.ResponseWriter, r *http.Request) { keys := r.URL.Query() from := keys.Get("from") filter := keys.Get("filter") - processRequest(w, r, "streams", from+"_"+filter, false) + utils.EncodeTwoStrings(from,filter) + encoded := utils.EncodeTwoStrings(from,filter) + processRequest(w, r, "streams", encoded, false) } diff --git a/broker/src/asapo_broker/server/process_request_test.go b/broker/src/asapo_broker/server/process_request_test.go index 781a7f16b2df345e4c1cb77aca727f7ec7a85608..cf0d41626723cd026791b9c9c9ac9471d78c0ce2 100644 --- a/broker/src/asapo_broker/server/process_request_test.go +++ b/broker/src/asapo_broker/server/process_request_test.go @@ -176,7 +176,7 @@ func (suite *ProcessRequestTestSuite) TestProcessRequestWithConnectionError() { w := doRequest("/beamtime/" + expectedBeamtimeId + "/" + expectedSource + "/" + expectedStream + "/" + expectedGroupID + "/next" + correctTokenSuffix) time.Sleep(time.Second) - suite.Equal(http.StatusNotFound, w.Code, "data not found") + suite.Equal(http.StatusServiceUnavailable, w.Code, "data not found") } func (suite *ProcessRequestTestSuite) TestProcessRequestWithInternalDBError() { @@ -191,7 +191,7 @@ func (suite *ProcessRequestTestSuite) TestProcessRequestWithInternalDBError() { w := doRequest("/beamtime/" + expectedBeamtimeId + "/" + expectedSource + "/" + expectedStream + "/" + expectedGroupID + "/next" + correctTokenSuffix) time.Sleep(time.Second) - suite.Equal(http.StatusNotFound, w.Code, "internal error") + suite.Equal(http.StatusServiceUnavailable, w.Code, "internal error") } func (suite *ProcessRequestTestSuite) TestProcessRequestAddsCounter() { diff --git a/broker/src/asapo_broker/server/request_common.go b/broker/src/asapo_broker/server/request_common.go index 17a4b30aedd417fd018b69382284ab5e305322f9..1a0d5e875034ed369f85128929ef3baae1c17c1f 100644 --- a/broker/src/asapo_broker/server/request_common.go +++ b/broker/src/asapo_broker/server/request_common.go @@ -16,7 +16,7 @@ func writeAuthAnswer(w http.ResponseWriter, requestName string, db_name string, case AuthorizationError: w.WriteHeader(er.statusCode) default: - w.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusServiceUnavailable) } w.Write([]byte(err.Error())) } diff --git a/broker/src/asapo_broker/server/server.go b/broker/src/asapo_broker/server/server.go index 1537fa08642c36454b679b54e218d5ad5b6f4007..f736671739406613093df46730000f3782421a61 100644 --- a/broker/src/asapo_broker/server/server.go +++ b/broker/src/asapo_broker/server/server.go @@ -2,14 +2,15 @@ package server import ( "asapo_broker/database" + "asapo_common/discovery" log "asapo_common/logger" "errors" - "io/ioutil" "net/http" ) const kDefaultresendInterval = 10 const kDefaultStreamCacheUpdateIntervalMs = 100 +const kDefaultTokenCacheUpdateIntervalMs = 60000 var db database.Agent @@ -25,6 +26,14 @@ type serverSettings struct { discoveredDbAddress string CheckResendInterval *int StreamCacheUpdateIntervalMs *int + TokenCacheUpdateIntervalMs *int +} + +func (s *serverSettings) GetTokenCacheUpdateInterval() int { + if s.TokenCacheUpdateIntervalMs == nil { + return kDefaultTokenCacheUpdateIntervalMs + } + return *s.TokenCacheUpdateIntervalMs } func (s *serverSettings) GetResendInterval() int { @@ -58,17 +67,7 @@ type discoveryAPI struct { baseURL string } -var discoveryService discoveryAPI - -func (api *discoveryAPI) GetMongoDbAddress() (string, error) { - resp, err := api.Client.Get(api.baseURL + "/asapo-mongodb") - if err != nil { - return "", err - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - return string(body), err -} +var discoveryService discovery.DiscoveryAPI func ReconnectDb() (err error) { if db == nil { @@ -97,7 +96,7 @@ func InitDB(dbAgent database.Agent) (err error) { } func CreateDiscoveryService() { - discoveryService = discoveryAPI{&http.Client{}, "http://" + settings.DiscoveryServer} + discoveryService = discovery.CreateDiscoveryService(&http.Client{},"http://" + settings.DiscoveryServer) } func CleanupDB() { diff --git a/broker/src/asapo_broker/server/server_nottested.go b/broker/src/asapo_broker/server/server_nottested.go index ae96af4d5a957f7bc5529ad0f78510a648cdabd0..ec512247a9d1ce7052f1ac32ad99ff205a434f12 100644 --- a/broker/src/asapo_broker/server/server_nottested.go +++ b/broker/src/asapo_broker/server/server_nottested.go @@ -7,6 +7,7 @@ import ( "asapo_common/utils" "errors" "net/http" + _ "net/http/pprof" "strconv" ) @@ -22,6 +23,7 @@ func Start() { StartStatistics() } mux := utils.NewRouter(listRoutes) + mux.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux) log.Info("Listening on port: " + strconv.Itoa(settings.Port)) log.Fatal(http.ListenAndServe(":"+strconv.Itoa(settings.Port), http.HandlerFunc(mux.ServeHTTP))) } diff --git a/broker/src/asapo_broker/server/server_test.go b/broker/src/asapo_broker/server/server_test.go index 3a7243782c43ca7e5aaae7c7a98f638c547c7ae3..fc593fd028796f6f6dd3b94ddd476b38bc2c3583 100644 --- a/broker/src/asapo_broker/server/server_test.go +++ b/broker/src/asapo_broker/server/server_test.go @@ -2,6 +2,7 @@ package server import ( "asapo_broker/database" + "asapo_common/discovery" "asapo_common/logger" "errors" "github.com/stretchr/testify/assert" @@ -77,7 +78,7 @@ func TestInitDBWithAutoAddress(t *testing.T) { })) defer mock_server.Close() - discoveryService = discoveryAPI{mock_server.Client(), mock_server.URL} + discoveryService = discovery.CreateDiscoveryService(mock_server.Client(), mock_server.URL) mock_db.On("Connect", "0.0.0.0:0000").Return(nil) mock_db.On("SetSettings", mock.Anything).Return() @@ -95,7 +96,7 @@ func TestReconnectDB(t *testing.T) { assert.Equal(t, req.URL.String(), "/asapo-mongodb", "request string") rw.Write([]byte(mongo_address)) })) - discoveryService = discoveryAPI{mock_server.Client(), mock_server.URL} + discoveryService = discovery.CreateDiscoveryService(mock_server.Client(), mock_server.URL) defer mock_server.Close() diff --git a/common/cpp/include/asapo/common/io_error.h b/common/cpp/include/asapo/common/io_error.h index 7375ec548a3d0c00665c30cc27489e434c77cb33..e11399e00bcbe73554358d49a26f4da301457910 100644 --- a/common/cpp/include/asapo/common/io_error.h +++ b/common/cpp/include/asapo/common/io_error.h @@ -27,8 +27,8 @@ enum class IOErrorType { kSocketOperationUnknownAtLevel, kSocketOperationValueOutOfBound, kAddressNotValid, - kBrokenPipe - + kBrokenPipe, + kNotConnected }; using IOError = ServiceError<IOErrorType, ErrorType::kIOError>; @@ -67,6 +67,10 @@ auto const kAddressAlreadyInUse = IOErrorTemplate { auto const kConnectionRefused = IOErrorTemplate { "Connection refused", IOErrorType::kConnectionRefused }; +auto const kNotConnected = IOErrorTemplate { + "Not connected", IOErrorType::kNotConnected +}; + auto const kConnectionResetByPeer = IOErrorTemplate { "kConnectionResetByPeer", IOErrorType::kConnectionResetByPeer }; diff --git a/common/cpp/src/logger/spd_logger.cpp b/common/cpp/src/logger/spd_logger.cpp index 9228b8f6104db2154d7c17718f2c903209c23b9e..4cc5c49b098abdbb2ef4321642055c64ca913943 100644 --- a/common/cpp/src/logger/spd_logger.cpp +++ b/common/cpp/src/logger/spd_logger.cpp @@ -2,6 +2,10 @@ #include "fluentd_sink.h" +#include <sstream> +#include <iomanip> + + namespace asapo { void SpdLogger::SetLogLevel(LogLevel level) { @@ -25,9 +29,47 @@ void SpdLogger::SetLogLevel(LogLevel level) { } } } + +std::string escape_json(const std::string& s) { + std::ostringstream o; + for (auto c = s.cbegin(); c != s.cend(); c++) { + switch (*c) { + case '"': + o << "\\\""; + break; + case '\\': + o << "\\\\"; + break; + case '\b': + o << "\\b"; + break; + case '\f': + o << "\\f"; + break; + case '\n': + o << "\\n"; + break; + case '\r': + o << "\\r"; + break; + case '\t': + o << "\\t"; + break; + default: + if ('\x00' <= *c && *c <= '\x1f') { + o << "\\u" + << std::hex << std::setw(4) << std::setfill('0') << (int)*c; + } else { + o << *c; + } + } + } + return o.str(); +} + std::string EncloseMsg(std::string msg) { if (msg.find("\"") != 0) { - return std::string(R"("message":")") + msg + "\""; + return std::string(R"("message":")") + escape_json(msg) + "\""; } else { return msg; } diff --git a/common/cpp/src/system_io/system_io_linux_mac.cpp b/common/cpp/src/system_io/system_io_linux_mac.cpp index b933a506eeb3e4bc4131dc81ec182c24f1f39d41..9d36dca7b3830fdd7af0221e203cca47d2ab669c 100644 --- a/common/cpp/src/system_io/system_io_linux_mac.cpp +++ b/common/cpp/src/system_io/system_io_linux_mac.cpp @@ -50,6 +50,8 @@ Error GetLastErrorFromErrno() { return IOErrorTemplates::kFileAlreadyExists.Generate(); case ENOSPC: return IOErrorTemplates::kNoSpaceLeft.Generate(); + case ENOTCONN: + return IOErrorTemplates::kNotConnected.Generate(); case ECONNREFUSED: return IOErrorTemplates::kConnectionRefused.Generate(); case EADDRINUSE: diff --git a/common/cpp/unittests/logger/test_logger.cpp b/common/cpp/unittests/logger/test_logger.cpp index 6dc03c84104187ee38fb8bfc2dd710d39062ed99..ed67359e1479f6d65fda1b7dc1d7a8a1cb4e5382 100644 --- a/common/cpp/unittests/logger/test_logger.cpp +++ b/common/cpp/unittests/logger/test_logger.cpp @@ -71,11 +71,11 @@ class LoggerTests : public Test { spdlog::details::log_msg msg; spdlog::details::log_msg msg_json; - std::string test_string{"Hello"}; + std::string test_string{"Hello\""}; std::string test_string_json{R"("Hello":"test","int":1,"double":123.234)"}; void SetUp() override { - msg.raw << R"("message":"Hello")"; + msg.raw << R"("message":"Hello\"")"; msg_json.raw << R"("Hello":"test","int":1,"double":123.234)"; log.reset(new spdlog::logger("mylogger", mock_sink)); logger.log__ = std::move(log); diff --git a/common/go/src/asapo_common/discovery/discovery.go b/common/go/src/asapo_common/discovery/discovery.go new file mode 100644 index 0000000000000000000000000000000000000000..552c07cf91d29b1b1747632bb96b5418bb59c577 --- /dev/null +++ b/common/go/src/asapo_common/discovery/discovery.go @@ -0,0 +1,29 @@ +package discovery + +import ( + "io/ioutil" + "net/http" + "errors" +) + +type DiscoveryAPI struct { + client *http.Client + baseURL string +} + +func (api *DiscoveryAPI) GetMongoDbAddress() (string, error) { + resp, err := api.client.Get(api.baseURL + "/asapo-mongodb") + if err != nil { + return "", err + } + if resp.StatusCode!=http.StatusOK { + return "", errors.New("cannot get mongodb server, status: "+resp.Status) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + return string(body), err +} + +func CreateDiscoveryService(client *http.Client,uri string) DiscoveryAPI{ + return DiscoveryAPI{client, uri} +} \ No newline at end of file diff --git a/common/go/src/asapo_common/utils/authorization.go b/common/go/src/asapo_common/utils/authorization.go index c913611730477d2611c0e6410a77bbf25e72f05b..d707819b9d11758a87f5f3538b204e8d76ed5ee3 100644 --- a/common/go/src/asapo_common/utils/authorization.go +++ b/common/go/src/asapo_common/utils/authorization.go @@ -17,7 +17,7 @@ type Auth interface { GenerateToken(...interface{}) (string, error) ProcessAuth(http.HandlerFunc, string) http.HandlerFunc Name() string - CheckAndGetContent(token string, extraClaims interface{}, payload ...interface{}) (string,error) + CheckAndGetContent(token string, extraClaims interface{}, payload ...interface{}) (*jwt.StandardClaims,error) } func SubjectFromBeamtime(bt string)string { @@ -147,23 +147,21 @@ func ProcessJWTAuth(fn http.HandlerFunc, key string) http.HandlerFunc { } } -func (a *JWTAuth) CheckAndGetContent(token string, extraClaims interface{}, payload ...interface{}) (subject string,err error) { +func (a *JWTAuth) CheckAndGetContent(token string, extraClaims interface{}, payload ...interface{}) (claims *jwt.StandardClaims, err error) { // payload ignored c, ok := CheckJWTToken(token,a.Key) if !ok { - return "",errors.New("wrong JWT token") + return nil,errors.New("wrong JWT token") } claim,ok := c.(*CustomClaims) if !ok { - return "",errors.New("cannot get CustomClaims") + return nil,errors.New("cannot get CustomClaims") } - subject = claim.Subject - if extraClaims!=nil { err = MapToStruct(claim.ExtraClaims.(map[string]interface{}), extraClaims) } - return subject,err + return &claim.StandardClaims,err } @@ -264,20 +262,22 @@ func ProcessHMACAuth(fn http.HandlerFunc, payload, key string) http.HandlerFunc } } -func (a *HMACAuth) CheckAndGetContent(token string, _ interface{}, payload ...interface{}) (string,error) { +func (a *HMACAuth) CheckAndGetContent(token string, _ interface{}, payload ...interface{}) (*jwt.StandardClaims,error) { if len(payload) != 1 { - return "",errors.New("wrong payload") + return nil,errors.New("wrong payload") } value, ok := payload[0].(string) if !ok { - return "",errors.New("wrong payload") + return nil,errors.New("wrong payload") } ok = CheckHMACToken(token,value,a.Key) if !ok { - return "",errors.New("wrong HMAC token") + return nil,errors.New("wrong HMAC token") } - return value,nil + claim := jwt.StandardClaims{} + claim.Subject = value + return &claim,nil } diff --git a/common/go/src/asapo_common/utils/helpers.go b/common/go/src/asapo_common/utils/helpers.go index 714cebf61ad85db215259c5147d06af861c51ad5..ed19b875d14bdcba203b19efaaf0d65798520933 100644 --- a/common/go/src/asapo_common/utils/helpers.go +++ b/common/go/src/asapo_common/utils/helpers.go @@ -2,9 +2,10 @@ package utils import ( json "encoding/json" + "errors" "io/ioutil" + "strconv" "strings" - "errors" ) func StringInSlice(a string, list []string) bool { @@ -102,3 +103,20 @@ func MapToStruct(m map[string]interface{}, val interface{}) error { } return nil } + +func EncodeTwoStrings(first,second string) string { + return strconv.Itoa(len(first))+"/"+first + second +} + +func DecodeTwoStrings(encoded string) (string,string,error) { + temp := strings.SplitN(encoded,"/",2); + if len(temp)!=2 { + return "","",errors.New("wrong input: "+encoded) + } + length,err := strconv.Atoi(temp[0]); + if err!=nil { + return "","",errors.New("wrong input: "+encoded) + } + return temp[1][:length],temp[1][length:],err + +} \ No newline at end of file diff --git a/common/go/src/asapo_common/utils/helpers_test.go b/common/go/src/asapo_common/utils/helpers_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6c817f837ec34759c66e4b1d9f4cd933a2f84068 --- /dev/null +++ b/common/go/src/asapo_common/utils/helpers_test.go @@ -0,0 +1,33 @@ +package utils + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + + + +var encodeTests = []struct { + input string + str1 string + str2 string + message string +}{ + {"5/hellohello1","hello", "hello1","ok"}, + {"0/","", "","ok"}, + {"5/hello","hello", "","ok"}, + {"10/hello_testall","hello_test", "all","ok"}, + +} + +func TestEncodeDecode(t *testing.T) { + for _, test := range encodeTests { + encoded:=EncodeTwoStrings(test.str1,test.str2) + assert.Equal(t, test.input,encoded,test.message) + s1,s2,err:=DecodeTwoStrings(test.input) + assert.Equal(t, test.str1,s1,test.message) + assert.Equal(t, test.str2,s2,test.message) + assert.NoError(t, err,test.message) + } +} + diff --git a/common/go/src/asapo_common/utils/status_codes.go b/common/go/src/asapo_common/utils/status_codes.go index 7002a963e250b20b9af88c1125b54c2993215aca..fdd205418a2fb6abd8e22078bdb104e35fa5ad71 100644 --- a/common/go/src/asapo_common/utils/status_codes.go +++ b/common/go/src/asapo_common/utils/status_codes.go @@ -9,7 +9,7 @@ const ( const ( //error codes StatusTransactionInterrupted = http.StatusInternalServerError - StatusServiceUnavailable = http.StatusNotFound + StatusServiceUnavailable = http.StatusServiceUnavailable StatusWrongInput = http.StatusBadRequest StatusNoData = http.StatusConflict StatusPartialData = http.StatusPartialContent diff --git a/consumer/api/cpp/include/asapo/consumer/consumer_error.h b/consumer/api/cpp/include/asapo/consumer/consumer_error.h index 55618fddc9ea98dd8f68cbf0bfa38821d02f72d8..efa0631dab069a96595de024b4873d2d36e68235 100644 --- a/consumer/api/cpp/include/asapo/consumer/consumer_error.h +++ b/consumer/api/cpp/include/asapo/consumer/consumer_error.h @@ -41,8 +41,6 @@ auto const kPartialData = ConsumerErrorTemplate { "partial data", ConsumerErrorType::kPartialData }; - - auto const kLocalIOError = ConsumerErrorTemplate { "local i/o error", ConsumerErrorType::kLocalIOError }; @@ -76,8 +74,6 @@ auto const kUnavailableService = ConsumerErrorTemplate { "service unavailable", ConsumerErrorType::kUnavailableService }; - - } } diff --git a/consumer/api/cpp/src/consumer_impl.cpp b/consumer/api/cpp/src/consumer_impl.cpp index 973e6a98a5313364d75c2455662476fd25e2401f..659ad07a261974ce1cf0dba75cddc107cdb463a7 100644 --- a/consumer/api/cpp/src/consumer_impl.cpp +++ b/consumer/api/cpp/src/consumer_impl.cpp @@ -91,6 +91,8 @@ Error ConsumerErrorFromHttpCode(const RequestOutput* response, const HttpCode& c return ConsumerErrorTemplates::kWrongInput.Generate(response->to_string()); case HttpCode::InternalServerError: return ConsumerErrorTemplates::kInterruptedTransaction.Generate(response->to_string()); + case HttpCode::ServiceUnavailable: + return ConsumerErrorTemplates::kUnavailableService.Generate(response->to_string()); case HttpCode::NotFound: return ConsumerErrorTemplates::kUnavailableService.Generate(response->to_string()); case HttpCode::Conflict: diff --git a/consumer/api/cpp/unittests/test_consumer_impl.cpp b/consumer/api/cpp/unittests/test_consumer_impl.cpp index ae11c5592d26e5e7746cf4ad9159f8d439fddb36..0b783b42bf055819ae9f07860b4bdb3d34414c66 100644 --- a/consumer/api/cpp/unittests/test_consumer_impl.cpp +++ b/consumer/api/cpp/unittests/test_consumer_impl.cpp @@ -146,7 +146,7 @@ class ConsumerImplTests : public Test { void MockGetError() { EXPECT_CALL(mock_http_client, Get_t(HasSubstr(expected_broker_api), _, _)).WillOnce(DoAll( - SetArgPointee<1>(HttpCode::NotFound), + SetArgPointee<1>(HttpCode::ServiceUnavailable), SetArgPointee<2>(asapo::IOErrorTemplates::kUnknownIOError.Generate().release()), Return("") )); @@ -442,7 +442,7 @@ TEST_F(ConsumerImplTests, GetMessageReturnsNoDataAfterTimeoutEvenIfOtherErrorOcc "/stream/0/" + std::to_string(expected_dataset_id) + "?token=" + expected_token, _, _)).Times(AtLeast(1)).WillRepeatedly(DoAll( - SetArgPointee<1>(HttpCode::NotFound), + SetArgPointee<1>(HttpCode::ServiceUnavailable), SetArgPointee<2>(nullptr), Return(""))); @@ -1400,7 +1400,7 @@ TEST_F(ConsumerImplTests, NegativeAcknowledgeUsesCorrectUri) { TEST_F(ConsumerImplTests, CanInterruptOperation) { EXPECT_CALL(mock_http_client, Get_t(_, _, _)).Times(AtLeast(1)).WillRepeatedly(DoAll( - SetArgPointee<1>(HttpCode::NotFound), + SetArgPointee<1>(HttpCode::ServiceUnavailable), SetArgPointee<2>(nullptr), Return(""))); diff --git a/deploy/asapo_helm_chart/asapo/configs/asapo-authorizer.json b/deploy/asapo_helm_chart/asapo/configs/asapo-authorizer.json index b6c3d5534036a419d0ba4ae7462bb569c00c7030..db3745ac6dfe47362541d0413508dd13714ef655 100644 --- a/deploy/asapo_helm_chart/asapo/configs/asapo-authorizer.json +++ b/deploy/asapo_helm_chart/asapo/configs/asapo-authorizer.json @@ -14,5 +14,6 @@ "Uri" : "ldap://localhost:389", "BaseDn" : "ou=rgy,o=desy,c=de", "FilterTemplate" : "(cn=a3__BEAMLINE__-hosts)" - } + }, + "UpdateRevokedTokensIntervalSec": 60 } diff --git a/deploy/asapo_services/asap3.tfvars b/deploy/asapo_services/asap3.tfvars index 4ff7c3cb46700d4f61dc52eea6f5e3524fc267ab..1070bcc4026d94dd2223a0bbaae0951dacc22fa1 100644 --- a/deploy/asapo_services/asap3.tfvars +++ b/deploy/asapo_services/asap3.tfvars @@ -1,4 +1,5 @@ elk_logs = true +perf_monitor = true asapo_imagename_suffix = "" asapo_image_tag = "" @@ -26,8 +27,8 @@ influxdb_total_memory_size = 2000 fluentd_total_memory_size = 1000 elasticsearch_total_memory_size = 3000 kibana_total_memory_size = 1000 -mongo_total_memory_size = 20000 -authorizer_total_memory_size = 512 +mongo_total_memory_size = 40000 +authorizer_total_memory_size = 20000 discovery_total_memory_size = 512 n_receivers = 2 diff --git a/deploy/asapo_services/scripts/asapo-logging.nmd.tpl b/deploy/asapo_services/scripts/asapo-logging.nmd.tpl index 3252f86773cdc6f57a0b244ec47ef24a3ed1869d..a033d9bb8764a63a5363e520663301b2252b631d 100644 --- a/deploy/asapo_services/scripts/asapo-logging.nmd.tpl +++ b/deploy/asapo_services/scripts/asapo-logging.nmd.tpl @@ -170,12 +170,12 @@ job "asapo-logging" { name = "alive" type = "http" path = "/logsview" - interval = "10s" + interval = "60s" timeout = "1s" } check_restart { limit = 2 - grace = "90s" + grace = "600s" ignore_warnings = false } } diff --git a/deploy/asapo_services/scripts/asapo-mongo.nmd.tpl b/deploy/asapo_services/scripts/asapo-mongo.nmd.tpl index 9c06fa7da69beeec0fee5a4f6a0b51725ef36403..7fefa0d18462177b604ed607ece288d3255e23e2 100644 --- a/deploy/asapo_services/scripts/asapo-mongo.nmd.tpl +++ b/deploy/asapo_services/scripts/asapo-mongo.nmd.tpl @@ -56,7 +56,7 @@ job "asapo-mongo" { } check_restart { limit = 2 - grace = "90s" + grace = "1800s" ignore_warnings = false } } diff --git a/deploy/asapo_services/scripts/authorizer.json.tpl b/deploy/asapo_services/scripts/authorizer.json.tpl index 3aecc0df652245b0f4c51ed5008301f17cee10fd..1d77de613c0a03883370bb4812b92b83f22d89b9 100644 --- a/deploy/asapo_services/scripts/authorizer.json.tpl +++ b/deploy/asapo_services/scripts/authorizer.json.tpl @@ -14,5 +14,8 @@ "Uri" : "{{ env "NOMAD_META_ldap_uri" }}", "BaseDn" : "ou=rgy,o=desy,c=de", "FilterTemplate" : "(cn=a3__BEAMLINE__-hosts)" - } + }, + "DatabaseServer":"auto", + "DiscoveryServer": "localhost:8400/asapo-discovery", + "UpdateRevokedTokensIntervalSec": 60 } diff --git a/deploy/asapo_services/scripts/fluentd.conf.tpl b/deploy/asapo_services/scripts/fluentd.conf.tpl index c97a9048e4be3f3114fd59c225be2ca433910cef..4224257406b115139d958e77d807edf43423aa91 100644 --- a/deploy/asapo_services/scripts/fluentd.conf.tpl +++ b/deploy/asapo_services/scripts/fluentd.conf.tpl @@ -40,12 +40,19 @@ host localhost port 8400 path /elasticsearch/ - flush_interval 5s logstash_format true time_key_format %Y-%m-%dT%H:%M:%S.%N time_key time time_key_exclude_timestamp true - buffer_type memory + <buffer> + @type memory + total_limit_size 100MB + flush_mode interval + flush_interval 1s + flush_thread_count 3 + chunk_limit_size 500KB + overflow_action drop_oldest_chunk + </buffer> </store> {{ end }} <store> @@ -56,4 +63,3 @@ path /shared/asapo-logs </store> </match> - diff --git a/deploy/asapo_services/scripts/nginx.conf.tpl b/deploy/asapo_services/scripts/nginx.conf.tpl index e0c52d1ed7dfab0791f81cb226e5fa2d81a43d59..03bc7377435d913959c003949822b9e7f230d293 100644 --- a/deploy/asapo_services/scripts/nginx.conf.tpl +++ b/deploy/asapo_services/scripts/nginx.conf.tpl @@ -19,6 +19,7 @@ http { client_body_temp_path "/tmp/client_body" 1 2; + client_max_body_size 10M; proxy_temp_path "/tmp/proxy" 1 2; fastcgi_temp_path "/tmp/fastcgi" 1 2; scgi_temp_path "/tmp/scgi" 1 2; 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 \ .. diff --git a/discovery/src/asapo_discovery/protocols/hard_coded_producer.go b/discovery/src/asapo_discovery/protocols/hard_coded_producer.go index 7207d0547d80767119b324b53e1ad2dd33d82d16..9ef311c1261095631feb7996fc68f06b38a66181 100644 --- a/discovery/src/asapo_discovery/protocols/hard_coded_producer.go +++ b/discovery/src/asapo_discovery/protocols/hard_coded_producer.go @@ -2,11 +2,16 @@ package protocols func GetSupportedProducerProtocols() []Protocol { return []Protocol{ + Protocol{"v0.4", + map[string]string{ + "Discovery": "v0.1", + "Receiver": "v0.4", + }, &protocolValidatorCurrent{}}, Protocol{"v0.3", map[string]string{ "Discovery": "v0.1", "Receiver": "v0.3", - }, &protocolValidatorCurrent{}}, + }, &protocolValidatorDeprecated{getTimefromDate("2022-09-01")}}, Protocol{"v0.2", map[string]string{ "Discovery": "v0.1", diff --git a/discovery/src/asapo_discovery/protocols/protocol_test.go b/discovery/src/asapo_discovery/protocols/protocol_test.go index 344699581b2a09b20a7db3f72170dd4ca79d953b..fbf1bc205d66e7a0241b83f75dfcda8134046171 100644 --- a/discovery/src/asapo_discovery/protocols/protocol_test.go +++ b/discovery/src/asapo_discovery/protocols/protocol_test.go @@ -22,8 +22,10 @@ var protocolTests = []protocolTest{ {"consumer", "v1000.2", false, "unknown", "unknown protocol"}, + // producer - {"producer", "v0.3", true, "current", "v0.3"}, + {"producer", "v0.4", true, "current", "v0.4"}, + {"producer", "v0.3", true, "deprecates", "v0.3"}, {"producer", "v0.2", true, "deprecates", "v0.2"}, {"producer", "v0.1", true, "deprecates", "v0.1"}, {"producer", "v1000.2", false, "unknown", "unknown protocol"}, diff --git a/discovery/src/asapo_discovery/server/server_nottested.go b/discovery/src/asapo_discovery/server/server_nottested.go index 091db8e202b96c25b357139a147c236b1a3a3493..2c96a7c9cafd0e7e31b8380896d7a7724c05f027 100644 --- a/discovery/src/asapo_discovery/server/server_nottested.go +++ b/discovery/src/asapo_discovery/server/server_nottested.go @@ -8,10 +8,12 @@ import ( "asapo_common/version" "net/http" "strconv" + _ "net/http/pprof" ) func Start() { mux := utils.NewRouter(listRoutes) + mux.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux) log.Info("Starting ASAPO Discovery, version " + version.GetVersion()) log.Info("Listening on port: " + strconv.Itoa(settings.Port)) log.Fatal(http.ListenAndServe(":"+strconv.Itoa(settings.Port), http.HandlerFunc(mux.ServeHTTP))) diff --git a/docs/site/examples/start_asapo_socket.sh b/docs/site/examples/start_asapo_socket.sh old mode 100755 new mode 100644 diff --git a/file_transfer/src/asapo_file_transfer/server/server_nottested.go b/file_transfer/src/asapo_file_transfer/server/server_nottested.go index a55f7df422932df339e39f61d2836fb91e2d06df..c6472e77d64d90ebeb2d2e6e88d5a49399b48d22 100644 --- a/file_transfer/src/asapo_file_transfer/server/server_nottested.go +++ b/file_transfer/src/asapo_file_transfer/server/server_nottested.go @@ -9,12 +9,15 @@ import ( "errors" "net/http" "strconv" + _ "net/http/pprof" + ) func Start() { mux := utils.NewRouter(listRoutes) log.Info("Starting ASAPO Authorizer, version " + version.GetVersion()) log.Info("Listening on port: " + strconv.Itoa(settings.Port)) + mux.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux) log.Fatal(http.ListenAndServe(":"+strconv.Itoa(settings.Port), utils.ProcessJWTAuth(mux.ServeHTTP, settings.key))) } diff --git a/producer/api/cpp/unittests/test_producer_impl.cpp b/producer/api/cpp/unittests/test_producer_impl.cpp index 3ba5836b150644a76bf5c3f77ed02be868eb56e9..e62c21735ead0d311de4b2cf6a221e0ad244679e 100644 --- a/producer/api/cpp/unittests/test_producer_impl.cpp +++ b/producer/api/cpp/unittests/test_producer_impl.cpp @@ -602,10 +602,10 @@ TEST_F(ProducerImplTests, ReturnDataIfCanotAddToQueue) { TEST_F(ProducerImplTests, GetVersionInfoWithServer) { std::string result = - R"({"softwareVersion":"21.06.0, build 7a9294ad","clientSupported":"no", "clientProtocol":{"versionInfo":"v0.3"}})"; + R"({"softwareVersion":"21.06.0, build 7a9294ad","clientSupported":"no", "clientProtocol":{"versionInfo":"v0.4"}})"; EXPECT_CALL(*mock_http_client, Get_t(HasSubstr(expected_server_uri + - "/asapo-discovery/v0.1/version?client=producer&protocol=v0.3"), _, _)).WillOnce(DoAll( + "/asapo-discovery/v0.1/version?client=producer&protocol=v0.4"), _, _)).WillOnce(DoAll( SetArgPointee<1>(asapo::HttpCode::OK), SetArgPointee<2>(nullptr), Return(result))); @@ -614,7 +614,7 @@ TEST_F(ProducerImplTests, GetVersionInfoWithServer) { auto err = producer.GetVersionInfo(&client_info, &server_info, nullptr); ASSERT_THAT(err, Eq(nullptr)); ASSERT_THAT(server_info, HasSubstr("21.06.0")); - ASSERT_THAT(server_info, HasSubstr("v0.3")); + ASSERT_THAT(server_info, HasSubstr("v0.4")); } MATCHER_P4(M_CheckDeleteStreamRequest, op_code, source_credentials, stream, flag, diff --git a/producer/api/cpp/unittests/test_producer_request.cpp b/producer/api/cpp/unittests/test_producer_request.cpp index cf0b8aba9a20becec1928e260146699087d7cff4..7a48b685d332754b6c4bee9c99aefabd14b5a794 100644 --- a/producer/api/cpp/unittests/test_producer_request.cpp +++ b/producer/api/cpp/unittests/test_producer_request.cpp @@ -40,7 +40,7 @@ TEST(ProducerRequest, Constructor) { uint64_t expected_file_size = 1337; uint64_t expected_meta_size = 137; std::string expected_meta = "meta"; - std::string expected_api_version = "v0.3"; + std::string expected_api_version = "v0.4"; asapo::Opcode expected_op_code = asapo::kOpcodeTransferData; asapo::GenericRequestHeader header{expected_op_code, expected_file_id, expected_file_size, diff --git a/producer/api/cpp/unittests/test_receiver_discovery_service.cpp b/producer/api/cpp/unittests/test_receiver_discovery_service.cpp index d94988a48a1ee545ef9a473ff3684d35ea6d544f..0c501e6329373afbb3a796486355bfe891860756 100644 --- a/producer/api/cpp/unittests/test_receiver_discovery_service.cpp +++ b/producer/api/cpp/unittests/test_receiver_discovery_service.cpp @@ -48,7 +48,7 @@ class ReceiversStatusTests : public Test { NiceMock<asapo::MockLogger> mock_logger; NiceMock<MockHttpClient>* mock_http_client; - std::string expected_endpoint{"endpoint/asapo-discovery/v0.1/asapo-receiver?protocol=v0.3"}; + std::string expected_endpoint{"endpoint/asapo-discovery/v0.1/asapo-receiver?protocol=v0.4"}; ReceiverDiscoveryService status{"endpoint", 20}; void SetUp() override { diff --git a/receiver/src/request_handler/request_handler_authorize.cpp b/receiver/src/request_handler/request_handler_authorize.cpp index d171bebca59cf3ec98ff178b2f33c8bf192a13cd..0230988c1920527393d390d486fff1423d668ecf 100644 --- a/receiver/src/request_handler/request_handler_authorize.cpp +++ b/receiver/src/request_handler/request_handler_authorize.cpp @@ -29,8 +29,9 @@ Error RequestHandlerAuthorize::ErrorFromAuthorizationServerResponse(const Error& } } -Error CheckAccessType(const std::vector<std::string>& access_types) { - if(std::find(access_types.begin(), access_types.end(), "write") != access_types.end()) { +Error CheckAccessType(SourceType source_type, const std::vector<std::string>& access_types) { + if(std::find(access_types.begin(), access_types.end(), + source_type == SourceType::kProcessed ? "write" : "writeraw") != access_types.end()) { return nullptr; } else { return asapo::ReceiverErrorTemplates::kAuthorizationFailure.Generate("wrong access types"); @@ -42,7 +43,6 @@ Error RequestHandlerAuthorize::Authorize(Request* request, const char* source_cr HttpCode code; Error err; std::string request_string = GetRequestString(request, source_credentials); - auto response = http_client__->Post(GetReceiverConfig()->authorization_server + "/authorize", "", request_string, &code, &err); if (err || code != HttpCode::OK) { @@ -69,7 +69,7 @@ Error RequestHandlerAuthorize::Authorize(Request* request, const char* source_cr return ErrorFromAuthorizationServerResponse(err, "", code); } - err = CheckAccessType(access_types); + err = CheckAccessType(source_type_, access_types); if (err) { log__->Error("failure authorizing at " + GetReceiverConfig()->authorization_server + " request: " + request_string + " - " + diff --git a/receiver/unittests/request_handler/test_request_handler_authorizer.cpp b/receiver/unittests/request_handler/test_request_handler_authorizer.cpp index b4488cd47942e5c72b89604b6537752e6d78fa0b..30069b6e2531890a4a8ac168f0ab092ea964e620 100644 --- a/receiver/unittests/request_handler/test_request_handler_authorizer.cpp +++ b/receiver/unittests/request_handler/test_request_handler_authorizer.cpp @@ -95,7 +95,7 @@ class AuthorizerHandlerTests : public Test { void TearDown() override { handler.http_client__.release(); } - void MockAuthRequest(bool error, HttpCode code = HttpCode::OK) { + void MockAuthRequest(bool error, HttpCode code = HttpCode::OK, bool opError = false) { if (error) { EXPECT_CALL(mock_http_client, Post_t(expected_authorization_server + "/authorize", _, expect_request_string, _, _)). WillOnce( @@ -128,7 +128,7 @@ class AuthorizerHandlerTests : public Test { HasSubstr(expected_data_source), HasSubstr(expected_producer_uri), HasSubstr(expected_authorization_server)))); - } else if (expected_access_type_str == "[\"write\"]") { + } else if (!opError) { EXPECT_CALL(mock_logger, Debug(AllOf(HasSubstr("authorized"), HasSubstr(expected_beamtime_id), HasSubstr(expected_beamline), @@ -142,7 +142,7 @@ class AuthorizerHandlerTests : public Test { } - Error MockFirstAuthorization(bool error, HttpCode code = HttpCode::OK) { + Error MockFirstAuthorization(bool error, HttpCode code = HttpCode::OK, bool opError = false) { EXPECT_CALL(*mock_request, GetOpCode()) .WillOnce(Return(asapo::kOpcodeAuthorize)) ; @@ -155,7 +155,7 @@ class AuthorizerHandlerTests : public Test { ; - MockAuthRequest(error, code); + MockAuthRequest(error, code, opError); return handler.ProcessRequest(mock_request.get()); } Error MockRequestAuthorization(bool error, HttpCode code = HttpCode::OK, bool set_request = true) { @@ -218,11 +218,32 @@ TEST_F(AuthorizerHandlerTests, AuthorizeOk) { TEST_F(AuthorizerHandlerTests, AuthorizeFailsOnWrongAccessType) { expected_access_type_str = "[\"read\"]"; - auto err = MockFirstAuthorization(false); + auto err = MockFirstAuthorization(false, HttpCode::OK, true); + + ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kAuthorizationFailure)); +} +TEST_F(AuthorizerHandlerTests, AuthorizeFailsOnWrongAccessTypeForRaw) { + expected_access_type_str = "[\"write\"]"; + expected_source_type_str = "raw"; + auto err = MockFirstAuthorization(false, HttpCode::OK, true); ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kAuthorizationFailure)); } +TEST_F(AuthorizerHandlerTests, AuthorizeFailsOnWrongAccessTypeForProcessed) { + expected_access_type_str = "[\"writeraw\"]"; + expected_source_type_str = "processed"; + auto err = MockFirstAuthorization(false, HttpCode::OK, true); + ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kAuthorizationFailure)); +} + +TEST_F(AuthorizerHandlerTests, AuthorizeOkForRaw) { + expected_access_type_str = "[\"writeraw\"]"; + expected_source_type_str = "raw"; + auto err = MockFirstAuthorization(false, HttpCode::OK, false); + ASSERT_THAT(err, Eq(nullptr)); +} + TEST_F(AuthorizerHandlerTests, ErrorOnSecondAuthorize) { MockFirstAuthorization(false); EXPECT_CALL(*mock_request, GetOpCode()) diff --git a/tests/automatic/authorizer/check_authorize/check_linux.sh b/tests/automatic/authorizer/check_authorize/check_linux.sh index ea92dadbc998cf9e6a37b71534d35d24287cf7c1..340c715bce4db2135a33d2015d171d6599d921c6 100644 --- a/tests/automatic/authorizer/check_authorize/check_linux.sh +++ b/tests/automatic/authorizer/check_authorize/check_linux.sh @@ -6,6 +6,7 @@ trap Cleanup EXIT Cleanup() { echo cleanup + echo "db.dropDatabase()" | mongo asapo_admin } mkdir -p /tmp/asapo/asap3/petra3/gpfs/p00/2019/comissioning/c20180508-000-COM20181 @@ -20,8 +21,10 @@ cp beamtime-metadata-11111112.json /tmp/asapo/beamline/p08/current/ AdminToken=$ASAPO_CREATE_TOKEN echo admin $AdminToken -curl -v --silent -H "Authorization: Bearer $AdminToken" --data '{"Subject": {"beamtimeId":"12345678"},"DaysValid":123,"AccessType":["read"]}' 127.0.0.1:8400/asapo-authorizer/admin/issue --stderr - | tee /dev/stderr | grep "bt_12345678" -curl -v --silent -H "Authorization: Bearer blabla" --data '{"Subject": {"beamtimeId":"12345678"},"DaysValid":123,"AccessType":["read"]}' 127.0.0.1:8400/asapo-authorizer/admin/issue --stderr - | tee /dev/stderr | grep "token does not match" +RevokeToken=$ASAPO_REVOKE_TOKEN + +curl -v --silent -H "Authorization: Bearer $AdminToken" --data '{"Subject": {"beamtimeId":"12345678"},"DaysValid":123,"AccessTypes":["read"]}' 127.0.0.1:8400/asapo-authorizer/admin/issue --stderr - | tee /dev/stderr | grep "bt_12345678" +curl -v --silent -H "Authorization: Bearer blabla" --data '{"Subject": {"beamtimeId":"12345678"},"DaysValid":123,"AccessTypes":["read"]}' 127.0.0.1:8400/asapo-authorizer/admin/issue --stderr - | tee /dev/stderr | grep "token does not match" curl -v --silent --data '{"SourceCredentials":"processed%c20180508-000-COM20181%%detector%","OriginHost":"127.0.0.1:5555"}' 127.0.0.1:8400/asapo-authorizer/authorize --stderr - | tee /dev/stderr | grep c20180508-000-COM20181 curl -v --silent --data '{"SourceCredentials":"processed%c20180508-000-COM20181%%detector%","OriginHost":"127.0.0.1:5555"}' 127.0.0.1:8400/asapo-authorizer/authorize --stderr - | tee /dev/stderr | grep p00 @@ -44,7 +47,8 @@ curl -v --silent --data "{\"SourceCredentials\":\"raw%11000016%%detector%${token token=$BLP07_TOKEN curl -v --silent --data "{\"SourceCredentials\":\"processed%auto%p07%detector%$token\",\"OriginHost\":\"bla\"}" 127.0.0.1:8400/asapo-authorizer/authorize --stderr - | tee /dev/stderr | grep 11111111 -curl -v --silent --data "{\"SourceCredentials\":\"raw%auto%p07%detector%$token\",\"OriginHost\":\"127.0.0.1:8400/asapo-authorizer\"}" 127.0.0.1:8400/asapo-authorizer/authorize --stderr - | tee /dev/stderr | grep 11111111 +curl -v --silent --data "{\"SourceCredentials\":\"raw%auto%p07%detector%\",\"OriginHost\":\"127.0.0.1:8400/asapo-authorizer\"}" 127.0.0.1:8400/asapo-authorizer/authorize --stderr - | tee /dev/stderr | grep writeraw +! curl -v --silent --data "{\"SourceCredentials\":\"raw%auto%p07%detector%$token\",\"OriginHost\":\"127.0.0.1:8400/asapo-authorizer\"}" 127.0.0.1:8400/asapo-authorizer/authorize --stderr - | tee /dev/stderr | grep writeraw curl -v --silent --data "{\"SourceCredentials\":\"raw%auto%p07%detector%$token\",\"OriginHost\":\"127.0.0.1:8400/asapo-authorizer\"}" 127.0.0.1:8400/asapo-authorizer/authorize --stderr - | tee /dev/stderr | grep p07 curl -v --silent --data "{\"SourceCredentials\":\"raw%auto%p07%detector%$token\",\"OriginHost\":\"127.0.0.1:8400/asapo-authorizer\"}" 127.0.0.1:8400/asapo-authorizer/authorize --stderr - | tee /dev/stderr | grep /asap3/petra3/gpfs/p07/2020/data/11111111 @@ -58,4 +62,18 @@ curl -v --silent --data "{\"SourceCredentials\":\"processed%auto%p07%detector%$t token=$BLP07_W_TOKEN curl -v --silent --data "{\"SourceCredentials\":\"processed%auto%p07%detector%$token\",\"OriginHost\":\"bla\"}" 127.0.0.1:8400/asapo-authorizer/authorize --stderr - | tee /dev/stderr | grep write + +#revocation +token=`curl --silent -H "Authorization: Bearer $AdminToken" --data '{"Subject": {"beamtimeId":"11000015"},"DaysValid":123,"AccessTypes":["read"]}' 127.0.0.1:8400/asapo-authorizer/admin/issue | jq -r .Token` +echo $token + +curl -v --silent --data "{\"SourceCredentials\":\"processed%11000015%auto%detector%$token\",\"OriginHost\":\"bla\"}" 127.0.0.1:8400/asapo-authorizer/authorize --stderr - | tee /dev/stderr | grep p00 + +#revoke token +curl -v --silent -H "Authorization: Bearer $RevokeToken" --data '{"Token": "'"$token"'"}' 127.0.0.1:8400/asapo-authorizer/admin/revoke | grep '"Revoked":true' + +sleep 1 + +curl -v --silent --data "{\"SourceCredentials\":\"processed%11000015%auto%detector%$token\",\"OriginHost\":\"bla\"}" 127.0.0.1:8400/asapo-authorizer/authorize --stderr - | tee /dev/stderr | grep 401 + rm -rf /tmp/asapo/asap3 /tmp/asapo/beamline \ No newline at end of file diff --git a/tests/automatic/common_scripts/start_services.bat b/tests/automatic/common_scripts/start_services.bat index 87f16087a311774bc134cc6092dd9a69ebabca8f..459fa99a075ae4bc19e2a6cf155524330576be90 100644 --- a/tests/automatic/common_scripts/start_services.bat +++ b/tests/automatic/common_scripts/start_services.bat @@ -1,10 +1,10 @@ c:\opt\consul\nomad run nginx.nmd c:\opt\consul\nomad run discovery.nmd -c:\opt\consul\nomad run authorizer.nmd ping 192.0.2.1 -n 1 -w 3000 > nul set i=0 +set started=0 :repeat set /a i=%i%+1 echo %i% @@ -13,10 +13,12 @@ if %i% EQU 20 ( ) ping 192.0.2.1 -n 1 -w 1000 >nul curl --silent --fail 127.0.0.1:8400/asapo-discovery/asapo-mongodb --stderr - | findstr 127.0.0.1 || goto :repeat -if %i% EQU 1 ( +if %started% EQU 0 ( + c:\opt\consul\nomad run authorizer.nmd c:\opt\consul\nomad run receiver_tcp.nmd c:\opt\consul\nomad run broker.nmd c:\opt\consul\nomad run file_transfer.nmd + set started=1 ping 192.0.2.1 -n 1 -w 3000 > nul ) curl --silent --fail 127.0.0.1:8400/asapo-discovery/v0.1/asapo-receiver?protocol=v0.1 --stderr - | findstr 127.0.0.1 || goto :repeat diff --git a/tests/automatic/common_scripts/start_services.sh b/tests/automatic/common_scripts/start_services.sh index 5c3f70a7138f9297d572559728bfbaf8c21ded42..944f3ed0bfdf42d0d4bab7ea7c736770202dbbfe 100755 --- a/tests/automatic/common_scripts/start_services.sh +++ b/tests/automatic/common_scripts/start_services.sh @@ -1,4 +1,3 @@ -nomad run authorizer.nmd nomad run discovery.nmd nomad run nginx.nmd @@ -11,7 +10,7 @@ do break done - +nomad run authorizer.nmd nomad run file_transfer.nmd nomad run receiver_tcp.nmd nomad run broker.nmd diff --git a/tests/automatic/high_avail/broker_mongo_restart/check_linux.sh b/tests/automatic/high_avail/broker_mongo_restart/check_linux.sh index 2e40ee88eaeb8155fd9de4e11c5bd7557ab84f41..fc8a1a4869bfab4e436535145bbd3d6249c59d0b 100755 --- a/tests/automatic/high_avail/broker_mongo_restart/check_linux.sh +++ b/tests/automatic/high_avail/broker_mongo_restart/check_linux.sh @@ -82,7 +82,7 @@ producerid=`echo $!` wait echo "Start consumer in $network_type mode" -$consumer_bin ${proxy_address} ${receiver_folder} ${beamtime_id} 2 $token 10000 0 &> output.txt & +$consumer_bin ${proxy_address} ${receiver_folder} ${beamtime_id} 2 $token 50000 0 &> output.txt & workerid=`echo $!` sleep 2 diff --git a/tests/automatic/high_avail/services_restart/check_linux.sh b/tests/automatic/high_avail/services_restart/check_linux.sh index 7043c1ac971b7c8dac5b21cd1b8b3bc02a384a8c..e3260de4c2d00bda08cb724e69af0e19ea69157e 100644 --- a/tests/automatic/high_avail/services_restart/check_linux.sh +++ b/tests/automatic/high_avail/services_restart/check_linux.sh @@ -25,6 +25,7 @@ Cleanup() { echo cleanup rm -rf ${receiver_folder} echo "db.dropDatabase()" | mongo ${beamtime_id}_detector + set +e influx -execute "drop database ${monitor_database_name}" } diff --git a/tests/automatic/settings/authorizer_settings.json.tpl.lin b/tests/automatic/settings/authorizer_settings.json.tpl.lin index 0980b029ab5f75ff7c2fc7fc4c6a735eb763ad40..dd30b9d3c0a6f7361f55b2a700cd39b37e5b3b67 100644 --- a/tests/automatic/settings/authorizer_settings.json.tpl.lin +++ b/tests/automatic/settings/authorizer_settings.json.tpl.lin @@ -15,5 +15,8 @@ "Uri" : "ldap://localhost:389", "BaseDn" : "ou=rgy,o=desy,c=de", "FilterTemplate" : "(cn=a3__BEAMLINE__-hosts)" - } + }, + "DatabaseServer":"auto", + "DiscoveryServer": "localhost:8400/asapo-discovery", + "UpdateRevokedTokensIntervalSec": 60 } diff --git a/tests/automatic/settings/authorizer_settings.json.tpl.win b/tests/automatic/settings/authorizer_settings.json.tpl.win index a34aeee04de2331356421a45113a7717f9ffb798..dff507941808259b140f48ad9ec816b643c57bb5 100644 --- a/tests/automatic/settings/authorizer_settings.json.tpl.win +++ b/tests/automatic/settings/authorizer_settings.json.tpl.win @@ -15,5 +15,8 @@ "Uri" : "ldap://localhost:389", "BaseDn" : "ou=rgy,o=desy,c=de", "FilterTemplate" : "(cn=a3__BEAMLINE__-hosts)" - } + }, + "DatabaseServer":"auto", + "DiscoveryServer": "localhost:8400/asapo-discovery", + "UpdateRevokedTokensIntervalSec": 60 }