diff --git a/.github/workflow/ci.yml b/.github/workflows/ci.yml similarity index 71% rename from .github/workflow/ci.yml rename to .github/workflows/ci.yml index abc620f..213f3e1 100644 --- a/.github/workflow/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,10 @@ name: Go CI -on: +on: + pull_request: + branches: [develop, master] push: - branches: - - 'main' - - 'develop' + branches: [develop, master] jobs: build-linux: @@ -13,12 +13,11 @@ jobs: steps: - uses: actions/checkout@v4.1.7 - name: Set up Go - uses: actions/setup-go@v5.0.2 + uses: actions/setup-go@v5.2.0 with: - go-version: '1.18' - cache: false + go-version: '1.21' - name: Build - run: go build -v ./... + run: go version && go build -v ./... build-windows: name: Build Ferrum on windows @@ -26,12 +25,11 @@ jobs: steps: - uses: actions/checkout@v4.1.7 - name: Set up Go - uses: actions/setup-go@v5.0.2 + uses: actions/setup-go@v5.2.0 with: - go-version: '1.18' - cache: false + go-version: '1.21' - name: Build - run: go build -v ./... + run: go version && go build -v ./... all-tests-linux: name: Run all tests on linux @@ -39,13 +37,15 @@ jobs: steps: - uses: actions/checkout@v4.1.7 - name: Set up Go - uses: actions/setup-go@v5.0.2 + uses: actions/setup-go@v5.2.0 + with: + go-version: '1.21' - name: Set up Redis Stack server run: docker compose up -d redis - name: Get Redis logs run: docker logs $(docker ps -aqf "name=wissance_ferrum_db") - name: Test all - run: go test -v ./... + run: go version && go mod tidy && go test -v ./... #all-tests-windows: # name: Run all tests on windows @@ -68,12 +68,11 @@ jobs: steps: - uses: actions/checkout@v4.1.7 - name: Set up Go - uses: actions/setup-go@v5.0.2 + uses: actions/setup-go@v5.2.0 with: - go-version: '1.18' - cache: false + go-version: '1.21' - name: Run golangci-lint uses: golangci/golangci-lint-action@v6.1.0 with: - version: v1.50.1 - args: --timeout 3m --config .golangci.yaml + version: v1.63.4 + args: --timeout 3m --config .golangci.yaml \ No newline at end of file diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..f44bf78 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,76 @@ +run: + # timeout for analysis, e.g. 30s, 5m, default is 1m + timeout: 30m + + modules-download-mode: readonly + + go: '1.21' + +output: + # colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions + # default is "colored-line-number" + formats: code-climate + +linters: + enable-all: false + disable: + - exhaustruct + - gofumpt + - testpackage + - depguard + - tagliatelle + - ireturn + - varnamelen + - wrapcheck + +linters-settings: + stylecheck: + # Select the Go version to target. The default is '1.13'. + # https://staticcheck.io/docs/options#checks + checks: [ "all", "-ST1000" ] + funlen: + lines: 100 + gci: + sections: + - standard + - default + - prefix(github.com/wissance/Ferrum) + gocyclo: + min-complexity: 5 + varnamelen: + ignore-names: + - id + ignore-decls: + - ok bool + wrapcheck: + ignorePackageGlobs: + - google.golang.org/grpc/status + - github.com/pkg/errors + - golang.org/x/sync/errgroup + gosec: + excludes: + - G204 + +issues: + exclude-rules: + - path: _test\.go + linters: + - containedctx + - gocyclo + - cyclop + - funlen + - goerr113 + - varnamelen + - staticcheck + - maintidx + - lll + - paralleltest + - dupl + - typecheck + - wsl + - govet + - path: main\.go + linters: + - gochecknoglobals + - lll + - funlen diff --git a/api/admin/cli/main.go b/api/admin/cli/main.go index 87b4995..85ec819 100644 --- a/api/admin/cli/main.go +++ b/api/admin/cli/main.go @@ -110,10 +110,10 @@ func main() { case operations.ClientResource: var clientNew data.Client if unmarshalErr := json.Unmarshal(value, &clientNew); unmarshalErr != nil { - log.Fatalf(sf.Format("json.Unmarshal failed: {0}", unmarshalErr.Error())) + log.Fatal(sf.Format("json.Unmarshal failed: {0}", unmarshalErr.Error())) } if createErr := manager.CreateClient(params, clientNew); createErr != nil { - log.Fatalf(sf.Format("CreateClient failed: {0}", createErr.Error())) + log.Fatal(sf.Format("CreateClient failed: {0}", createErr.Error())) } log.Print(sf.Format("Client: \"{0}\" successfully created", clientNew.Name)) diff --git a/api/admin/cli/operations/operations.go b/api/admin/cli/operations/operations.go index 808e153..1d120c5 100644 --- a/api/admin/cli/operations/operations.go +++ b/api/admin/cli/operations/operations.go @@ -4,18 +4,18 @@ type ResourceType string const ( RealmResource ResourceType = "realm" - ClientResource = "client" - UserResource = "user" - UserFederationConfigResource = "user_federation" + ClientResource ResourceType = "client" + UserResource ResourceType = "user" + UserFederationConfigResource ResourceType = "user_federation" ) type OperationType string const ( GetOperation OperationType = "get" - CreateOperation = "create" - DeleteOperation = "delete" - UpdateOperation = "update" - ChangePassword = "change_password" - ResetPassword = "reset_password" + CreateOperation OperationType = "create" + DeleteOperation OperationType = "delete" + UpdateOperation OperationType = "update" + ChangePassword OperationType = "change_password" + ResetPassword OperationType = "reset_password" ) diff --git a/api/rest/web_api_handler.go b/api/rest/web_api_handler.go index 70c8c84..d38b157 100644 --- a/api/rest/web_api_handler.go +++ b/api/rest/web_api_handler.go @@ -89,7 +89,7 @@ func (wCtx *WebApiContext) IssueNewToken(respWriter http.ResponseWriter, request issueTokens := false // 0. Check whether we deal with issuing a new token or refresh previous one isRefresh := isTokenRefreshRequest(&tokenGenerationData) - if isRefresh == true { + if isRefresh { // 1-2. Validate refresh token and check is it fresh enough session := (*wCtx.Security).GetSessionByRefreshToken(realm, &tokenGenerationData.RefreshToken) if session == nil { @@ -192,7 +192,7 @@ func (wCtx *WebApiContext) GetUserInfo(respWriter http.ResponseWriter, request * realm := vars[globals.RealmPathVar] if !Validate(realm) { wCtx.Logger.Debug(sf.Format("Get UserInfo: is invalid realmName: '{0}'", realm)) - status := http.StatusBadRequest + status = http.StatusBadRequest result := dto.ErrorDetails{Msg: sf.Format(errors.InvalidRealm, realm)} afterHandle(&respWriter, status, &result) return @@ -240,7 +240,6 @@ func (wCtx *WebApiContext) GetUserInfo(respWriter http.ResponseWriter, request * result = dto.ErrorDetails{Msg: errors.InvalidTokenMsg, Description: errors.InvalidTokenDesc} } else { user, _ := (*wCtx.DataProvider).GetUserById(realmPtr.Name, session.UserId) - status = http.StatusOK if user != nil { result = user.GetUserInfo() } @@ -434,6 +433,7 @@ func isTokenRefreshRequest(tokenIssueData *dto.TokenGenerationData) bool { } // reserved for future use +// nolint unused func getUserIP(r *http.Request) string { IPAddress := r.Header.Get("X-Real-Ip") if IPAddress == "" { diff --git a/api/routing_fuzzing_test.go b/api/routing_fuzzing_test.go index 280e31c..a7ed26b 100644 --- a/api/routing_fuzzing_test.go +++ b/api/routing_fuzzing_test.go @@ -1,6 +1,7 @@ package api import ( + "context" "encoding/base64" "encoding/json" "io" @@ -74,9 +75,9 @@ func FuzzTestIssueNewTokenWithWrongClientId(f *testing.F) { f.Add("") f.Add("0") f.Add("00") + f.Fuzz(func(t *testing.T, clientId string) { initApp(t) - t.Parallel() issueNewToken(t, clientId, testClient1Secret, "vano", "1234567890", 400) }) } @@ -85,9 +86,9 @@ func FuzzTestIssueNewTokenWithWrongClientSecret(f *testing.F) { f.Add("\x00fb6Z4RsOadVycQoeQiN57xpu8w8wplYz") f.Add("fb6Z4RsOadVycQoeQiN57xpu8w8wplYz_!") f.Add("") + f.Fuzz(func(t *testing.T, clientSecret string) { initApp(t) - t.Parallel() issueNewToken(t, testClient1, clientSecret, "vano", "1234567890", 400) }) } @@ -96,9 +97,9 @@ func FuzzTestIssueNewTokenWithWrongUsername(f *testing.F) { f.Add("\x00vano") f.Add("!") f.Add("") + f.Fuzz(func(t *testing.T, username string) { initApp(t) - t.Parallel() issueNewToken(t, testClient1, testClient1Secret, username, "1234567890", 401) }) } @@ -107,9 +108,9 @@ func FuzzTestIssueNewTokenWithWrongPassword(f *testing.F) { f.Add("\x001234567890") f.Add("!") f.Add("") + f.Fuzz(func(t *testing.T, password string) { initApp(t) - t.Parallel() issueNewToken(t, testClient1, testClient1Secret, "vano", password, 401) }) } @@ -118,9 +119,9 @@ func FuzzTestIntrospectTokenWithWrongClientId(f *testing.F) { f.Add("\x001234567890") f.Add("!") f.Add("") + f.Fuzz(func(t *testing.T, clientId string) { initApp(t) - t.Parallel() token := getToken(t) checkIntrospectToken(t, token.AccessToken, clientId, testClient1Secret, testRealm1, 401) }) @@ -130,9 +131,9 @@ func FuzzTestIntrospectTokenWithWrongSecret(f *testing.F) { f.Add("\x001234567890") f.Add("!") f.Add("") + f.Fuzz(func(t *testing.T, clientSecret string) { initApp(t) - t.Parallel() token := getToken(t) checkIntrospectToken(t, token.AccessToken, testClient1, clientSecret, testRealm1, 401) }) @@ -143,9 +144,9 @@ func FuzzTestIntrospectTokenWithWrongToken(f *testing.F) { f.Add("\x001234567890") f.Add("!") f.Add("") + f.Fuzz(func(t *testing.T, token string) { initApp(t) - t.Parallel() checkIntrospectToken(t, token, testClient1, testClient1Secret, testRealm1, 401) }) } @@ -156,9 +157,9 @@ func FuzzTestRefreshTokenWithWrongToken(f *testing.F) { f.Add("") f.Add("0") f.Add("00") + f.Fuzz(func(t *testing.T, token string) { initApp(t) - t.Parallel() refreshToken(t, testClient1, testClient1Secret, token, 401) }) } @@ -168,14 +169,13 @@ func FuzzTestGetUserInfoWithWrongToken(f *testing.F) { f.Add("00") f.Add(" ") f.Add("\n\n") + f.Fuzz(func(t *testing.T, token string) { + initApp(t) expectedStatusCode := 401 if !isTokenValid(t, token) || len(token) == 0 { expectedStatusCode = 400 } - initApp(t) - t.Parallel() - t.Helper() userInfoUrlTemplate := "{0}/auth/realms/{1}/protocol/openid-connect/userinfo/" doRequest( t, "GET", userInfoUrlTemplate, testRealm1, nil, @@ -188,7 +188,8 @@ func initApp(t *testing.T) application.AppRunner { t.Helper() app := application.CreateAppWithData(&httpAppConfig, &testServerData, testKey, true) t.Cleanup(func() { - app.Stop() + _, err := app.Stop(context.Background()) + require.NoError(t, err) }) res, err := app.Init() assert.True(t, res) @@ -229,12 +230,11 @@ func setGetTokenFormData(clientId, clientSecret, grantType, username, password, func doPostForm(t *testing.T, reqUrl string, urlData url.Values, expectedStatus int) *http.Response { t.Helper() - response, _ := http.PostForm(reqUrl, urlData) + response, err := http.PostForm(reqUrl, urlData) + require.NoError(t, err) if response != nil { require.Equal(t, response.StatusCode, expectedStatus) } - // todo(yurishang): sometimes there is an Net Op error when running a fuzz test - // in line 'response, _ := http.PostForm(reqUrl, urlData)' return response } @@ -309,6 +309,7 @@ func getDataFromResponse[TR dto.Token | dto.ErrorDetails](t *testing.T, response func isTokenValid(t *testing.T, token string) bool { // Checking that the token doesn't contains space characters only. // If yes, then the token is not valid - the expected status code is 400. Otherwise - 401. + t.Helper() pattern := "[ \n\t]+" match, _ := regexp.MatchString(pattern, token) return !match diff --git a/application/application.go b/application/application.go index 585523c..c4715ac 100644 --- a/application/application.go +++ b/application/application.go @@ -1,14 +1,15 @@ package application import ( + "context" "errors" "fmt" "io" - "io/ioutil" "net" "net/http" "os" "path/filepath" + "time" httpSwagger "github.com/swaggo/http-swagger" "github.com/wissance/Ferrum/globals" @@ -43,6 +44,8 @@ type Application struct { webApiContext *rest.WebApiContext logger *logging.AppLogger httpHandler *http.Handler + httpServer *http.Server + shutdownTimeout time.Duration } // CreateAppWithConfigs creates but not Init new Application as AppRunner @@ -82,13 +85,10 @@ func CreateAppWithData(appConfig *config.AppConfig, serverData *data.ServerData, * Return start result (true if Start was successful) and error (nil if start was successful) */ func (app *Application) Start() (bool, error) { - var err error - go func() { - err = app.startWebService() - if err != nil { - app.logger.Error(stringFormatter.Format("An error occurred during API Service Start")) - } - }() + err := app.startWebService() + if err != nil { + app.logger.Error(stringFormatter.Format("An error occurred during API Service Start")) + } return err == nil, err } @@ -128,9 +128,11 @@ func (app *Application) Init() (bool, error) { return false, errors.New("secret key is nil") } app.secretKey = key + app.shutdownTimeout = cfg.ServerCfg.ShutdownTimeout * time.Second } else { // init logger app.logger = logging.CreateLogger(&app.appConfig.Logging) + app.shutdownTimeout = 30 * time.Second app.logger.Init() } // common part: both configs and direct struct pass @@ -150,6 +152,8 @@ func (app *Application) Init() (bool, error) { app.logger.Error(stringFormatter.Format("An error occurred during rest api init: {0}", err.Error())) return false, err } + + app.httpServer = &http.Server{Handler: *app.httpHandler} return true, nil } @@ -158,7 +162,13 @@ func (app *Application) Init() (bool, error) { * Parameters : no * Returns result of app stop and error */ -func (app *Application) Stop() (bool, error) { +func (app *Application) Stop(ctx context.Context) (bool, error) { + ctx, cancel := context.WithTimeout(ctx, app.shutdownTimeout) + defer cancel() + err := app.httpServer.Shutdown(ctx) + if err != nil { + return false, err + } return true, nil } @@ -287,21 +297,32 @@ func (app *Application) startWebService() error { var err error addressTemplate := "{0}:{1}" address := stringFormatter.Format(addressTemplate, app.appConfig.ServerCfg.Address, app.appConfig.ServerCfg.Port) + listener, err := net.Listen("tcp", address) + if err != nil { + return err + } + app.httpServer.Addr = address switch app.appConfig.ServerCfg.Schema { //nolint:exhaustive case config.HTTP: app.logger.Info(stringFormatter.Format("Starting \"HTTP\" WEB API Service on address: \"{0}\"", address)) - err = http.ListenAndServe(address, *app.httpHandler) - if err != nil { - app.logger.Error(stringFormatter.Format("An error occurred during attempt to start \"HTTP\" WEB API Service: {0}", err.Error())) - } + go func() { + err = app.httpServer.Serve(listener) + if err != nil { + app.logger.Error( + stringFormatter.Format("An error occurred during attempt to start \"HTTP\" WEB API Service: {0}", err.Error())) + } + }() case config.HTTPS: app.logger.Info(stringFormatter.Format("Starting \"HTTPS\" REST API Service on address: \"{0}\"", address)) cert := app.appConfig.ServerCfg.Security.CertificateFile key := app.appConfig.ServerCfg.Security.KeyFile - err = http.ListenAndServeTLS(address, cert, key, *app.httpHandler) - if err != nil { - app.logger.Error(stringFormatter.Format("An error occurred during attempt tp start \"HTTPS\" REST API Service: {0}", err.Error())) - } + go func() { + err = app.httpServer.ServeTLS(listener, cert, key) + if err != nil { + app.logger.Error( + stringFormatter.Format("An error occurred during attempt tp start \"HTTPS\" REST API Service: {0}", err.Error())) + } + }() } return err } @@ -313,7 +334,7 @@ func (app *Application) readKey() []byte { return nil } - fileData, err := ioutil.ReadFile(absPath) + fileData, err := os.ReadFile(absPath) if err != nil { app.logger.Error(stringFormatter.Format("An error occurred during key file reading: {0}", err.Error())) return nil @@ -352,7 +373,7 @@ func (app *Application) getSwaggerAddress() string { if len(envAddr) > 0 { return envAddr } - + // 2. Get Address from Network Interfaces addresses, err := net.InterfaceAddrs() if err != nil { diff --git a/application/application_runner.go b/application/application_runner.go index fb17aa5..793905a 100644 --- a/application/application_runner.go +++ b/application/application_runner.go @@ -1,6 +1,8 @@ package application import ( + "context" + "github.com/wissance/Ferrum/logging" ) @@ -13,7 +15,7 @@ type AppRunner interface { // Start this function starts initialized application (must be called after Init) Start() (bool, error) // Stop function to stop application - Stop() (bool, error) + Stop(ctx context.Context) (bool, error) // Init function initializes application components Init() (bool, error) // GetLogger function that required after app initialized all components to log some additional information about application stop diff --git a/application/application_test.go b/application/application_test.go index 6a02e82..fa0f0d7 100644 --- a/application/application_test.go +++ b/application/application_test.go @@ -1,6 +1,7 @@ package application import ( + "context" "crypto/tls" "encoding/base64" "encoding/json" @@ -91,6 +92,7 @@ func TestApplicationOnHttps(t *testing.T) { } func testRunCommonTestCycleImpl(t *testing.T, appConfig *config.AppConfig, baseUrl string) { + ctx := context.Background() app := CreateAppWithData(appConfig, &testServerData, testKey, true) res, err := app.Init() assert.True(t, res) @@ -164,7 +166,7 @@ func testRunCommonTestCycleImpl(t *testing.T, appConfig *config.AppConfig, baseU response = refreshToken(t, baseUrl, realm, testClient1, testClient1Secret, token.RefreshToken) assert.Equal(t, response.Status, "200 OK") - res, err = app.Stop() + res, err = app.Stop(ctx) assert.True(t, res) assert.Nil(t, err) } diff --git a/config.json b/config.json index 6b877ac..2a54a35 100644 --- a/config.json +++ b/config.json @@ -3,7 +3,8 @@ "schema": "http", "address": "localhost", "port": 8182, - "secret_file": "./keyfile" + "secret_file": "./keyfile", + "shutdown_timeout": 30 }, "logging": { "level": "debug", diff --git a/config/logs_config.go b/config/logs_config.go index 9e9aa60..405863f 100644 --- a/config/logs_config.go +++ b/config/logs_config.go @@ -1,14 +1,13 @@ package config -//Composing structs for unmarshalling. Writer is lumberjack's setup struct. -//It's annotated for JSON out-of-the-box. -//Logrus is for logging level and log output settings. - +// Composing structs for unmarshalling. Writer is lumberjack's setup struct. +// It's annotated for JSON out-of-the-box. +// Logrus is for logging level and log output settings. type AppenderType string const ( RollingFile AppenderType = "rolling_file" - Console = "console" + Console AppenderType = "console" ) /*type GlobalConfig struct { diff --git a/config/server_config.go b/config/server_config.go index dfa552a..d88de5e 100644 --- a/config/server_config.go +++ b/config/server_config.go @@ -2,8 +2,10 @@ package config import ( "errors" - sf "github.com/wissance/stringFormatter" "os" + "time" + + sf "github.com/wissance/stringFormatter" ) type Schema string @@ -19,11 +21,12 @@ type SecurityConfig struct { } type ServerConfig struct { - Schema Schema `json:"schema" example:"http or https"` - Address string `json:"address" example:"127.0.0.1 or mydomain.com"` - Port int `json:"port" example:"8080"` - Security *SecurityConfig `json:"security"` - SecretFile string `json:"secret_file" example:"./keyfile"` + Schema Schema `json:"schema" example:"http or https"` + Address string `json:"address" example:"127.0.0.1 or mydomain.com"` + Port int `json:"port" example:"8080"` + Security *SecurityConfig `json:"security"` + SecretFile string `json:"secret_file" example:"./keyfile"` + ShutdownTimeout time.Duration `json:"shutdown_timeout"` } func (cfg *ServerConfig) Validate() error { diff --git a/config_docker_w_redis.json b/config_docker_w_redis.json index 40041d7..03728ec 100644 --- a/config_docker_w_redis.json +++ b/config_docker_w_redis.json @@ -3,7 +3,8 @@ "schema": "http", "address": "0.0.0.0", "port": 8182, - "secret_file": "./keyfile" + "secret_file": "./keyfile", + "shutdown_timeout": 30 }, "logging": { "level": "debug", diff --git a/config_w_redis.json b/config_w_redis.json index ce10a79..a697ddc 100644 --- a/config_w_redis.json +++ b/config_w_redis.json @@ -3,7 +3,8 @@ "schema": "http", "address": "127.0.0.1", "port": 8182, - "secret_file": "./keyfile" + "secret_file": "./keyfile", + "shutdown_timeout": 30 }, "logging": { "level": "debug", diff --git a/data/client.go b/data/client.go index 709dbbc..edb3423 100644 --- a/data/client.go +++ b/data/client.go @@ -9,7 +9,7 @@ type ClientType string const ( Public ClientType = "public" - Confidential = "confidential" + Confidential ClientType = "confidential" ) // Client is a realm client, represents an application nad set of rules for interacting with Authorization server diff --git a/data/keycloak_user.go b/data/keycloak_user.go index 78d3935..fc296d6 100644 --- a/data/keycloak_user.go +++ b/data/keycloak_user.go @@ -83,6 +83,7 @@ func (user *KeyCloakUser) SetPassword(password string, encoder *encoding.Passwor func (user *KeyCloakUser) GetId() uuid.UUID { idStrValue := getPathStringValue[string](user.rawData, "info.sub") id, err := uuid.Parse(idStrValue) + // nolint staticcheck if err != nil { // todo(UMV): think what to do here, return error! } @@ -132,11 +133,12 @@ func (user *KeyCloakUser) GetFederationId() string { func getPathStringValue[T any](rawData interface{}, path string) T { var result T mask, err := jp.ParseString(path) + // nolint staticcheck if err != nil { // todo(UMV): log and think what to do ... } res := mask.Get(rawData) - if res != nil && len(res) == 1 { + if len(res) == 1 { result = res[0].(T) } return result diff --git a/data/keycloak_user_test.go b/data/keycloak_user_test.go index 510a121..0792613 100644 --- a/data/keycloak_user_test.go +++ b/data/keycloak_user_test.go @@ -34,7 +34,6 @@ func TestInitUserWithJsonAndCheck(t *testing.T) { for _, tCase := range testCases { t.Run(tCase.name, func(t *testing.T) { - t.Parallel() jsonStr := sf.Format(tCase.userTemplate, tCase.userName, tCase.preferredUsername) var rawUserData interface{} err := json.Unmarshal([]byte(jsonStr), &rawUserData) diff --git a/go.mod b/go.mod index 0da8407..6cbfd0c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/wissance/Ferrum -go 1.19 +go 1.21 require ( github.com/go-ldap/ldap/v3 v3.4.8 @@ -44,7 +44,7 @@ require ( golang.org/x/crypto v0.27.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sys v0.25.0 // indirect - golang.org/x/tools v0.25.0 // indirect + golang.org/x/tools v0.24.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e5eb782..04c0b1e 100644 --- a/go.sum +++ b/go.sum @@ -11,7 +11,9 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/bsm/ginkgo/v2 v2.5.0 h1:aOAnND1T40wEdAtkGSkvSICWeQ8L3UASX7YVCqQx+eQ= +github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= github.com/bsm/gomega v1.20.0 h1:JhAwLmtRzXFTx2AkALSLa8ijZafntmhSoU63Ok18Uq8= +github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= @@ -237,7 +239,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -261,6 +264,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -308,8 +312,8 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/main.go b/main.go index 2508d98..c460bf5 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ package main import ( + "context" "flag" "fmt" "os" @@ -18,8 +19,10 @@ import ( const defaultConfig = "./config.json" -var configFile = flag.String("config", defaultConfig, "--config ./config_w_redis.json") -var devMode = flag.Bool("devmode", false, "-devmode") +var ( + configFile = flag.String("config", defaultConfig, "--config ./config_w_redis.json") + devMode = flag.Bool("devmode", false, "-devmode") +) // main is an authorization server entry point is starts and stops by signal Application /* Ferrum requires config to run via cmd line, if no config was provided defaultConfig is using @@ -35,6 +38,8 @@ func main() { done := make(chan bool, 1) signal.Notify(osSignal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) + ctx := context.Background() + app := application.CreateAppWithConfigs(*configFile, *devMode) _, initErr := app.Init() if initErr != nil { @@ -62,7 +67,7 @@ func main() { // server was started in separate goroutine, main thread is waiting for signal to stop <-done - res, err = app.Stop() + res, err = app.Stop(ctx) if !res { msg := stringFormatter.Format("An error occurred during stopping application, error is: {0}", err.Error()) fmt.Println(msg) diff --git a/managers/files/manager.go b/managers/files/manager.go index 3999d50..65695ec 100644 --- a/managers/files/manager.go +++ b/managers/files/manager.go @@ -19,8 +19,8 @@ type objectType string const ( Realm objectType = "realm" - Client = "client" - User = "user" + Client objectType = "client" + User objectType = "user" ) // FileDataManager is the simplest Data Storage without any dependencies, it uses single JSON file (it is users and clients RO auth server) @@ -110,7 +110,7 @@ func (mn *FileDataManager) GetUsers(realmName string) ([]data.User, error) { return users, nil } } - return nil, errors.NewObjectNotFoundError(User, "", sf.Format("get realm: {0} users", realmName)) + return nil, errors.NewObjectNotFoundError(string(User), "", sf.Format("get realm: {0} users", realmName)) } // GetClient function for getting Realm Client by name @@ -135,7 +135,7 @@ func (mn *FileDataManager) GetClient(realmName string, clientName string) (*data return &c, nil } } - return nil, errors.NewObjectNotFoundError(Client, clientName, sf.Format("realm: {0}", realmName)) + return nil, errors.NewObjectNotFoundError(string(Client), clientName, sf.Format("realm: {0}", realmName)) } // GetUser function for getting Realm User by userName @@ -159,7 +159,7 @@ func (mn *FileDataManager) GetUser(realmName string, userName string) (data.User return u, nil } } - return nil, errors.NewObjectNotFoundError(User, userName, sf.Format("realm: {0}", realmName)) + return nil, errors.NewObjectNotFoundError(string(User), userName, sf.Format("realm: {0}", realmName)) } // GetUserById function for getting Realm User by UserId (uuid) @@ -179,7 +179,7 @@ func (mn *FileDataManager) GetUserById(realmName string, userId uuid.UUID) (data return u, nil } } - return nil, errors.NewObjectNotFoundError(User, userId.String(), sf.Format("realm: {0}", realmName)) + return nil, errors.NewObjectNotFoundError(string(User), userId.String(), sf.Format("realm: {0}", realmName)) } // CreateRealm creates new data.Realm in a data store, receive realmData unmarshalled json in a data.Realm diff --git a/managers/files/manager_test.go b/managers/files/manager_test.go index ebb077f..b38147c 100644 --- a/managers/files/manager_test.go +++ b/managers/files/manager_test.go @@ -119,6 +119,7 @@ func checkRealm(t *testing.T, expected *data.Realm, actual *data.Realm) { assert.Equal(t, expected.RefreshTokenExpiration, actual.RefreshTokenExpiration) } +// nolint unused func checkClients(t *testing.T, expected *[]data.Client, actual *[]data.Client) { assert.Equal(t, len(*expected), len(*actual)) for _, e := range *expected { @@ -142,6 +143,7 @@ func checkClient(t *testing.T, expected *data.Client, actual *data.Client) { assert.Equal(t, expected.Auth.Value, actual.Auth.Value) } +// nolint unused func checkUsers(t *testing.T, expected *[]data.User, actual *[]data.User) { assert.Equal(t, len(*expected), len(*actual)) for _, e := range *expected { diff --git a/managers/redis/manager.go b/managers/redis/manager.go index f73d9ed..4d3873f 100644 --- a/managers/redis/manager.go +++ b/managers/redis/manager.go @@ -30,11 +30,11 @@ type objectType string const ( Realm objectType = "realm" - RealmClients = "realm clients" - RealmUsers = "realm users" - RealmUserFederationConfig = " realm user federation config" - Client = "client" - User = "user" + RealmClients objectType = "realm clients" + RealmUsers objectType = "realm users" + RealmUserFederationConfig objectType = " realm user federation config" + Client objectType = "client" + User objectType = "user" ) const defaultNamespace = "fe" @@ -253,7 +253,7 @@ func getMultipleRedisObjects[T any](redisClient *redis.Client, ctx context.Conte ) ([]T, error) { redisCmd := redisClient.MGet(ctx, objKey...) if redisCmd.Err() != nil { - //todo(UMV): print when this will be done https://github.com/Wissance/stringFormatter/issues/14 + // todo(UMV): print when this will be done https://github.com/Wissance/stringFormatter/issues/14 logger.Warn(sf.Format("An error occurred during fetching {0}: from Redis server", objName)) return nil, redisCmd.Err() } @@ -353,7 +353,8 @@ func getObjectsListOfNonSlicesItemsFromRedis[T any](redisClient *redis.Client, c } func updateObjectListItemInRedis[T any](redisClient *redis.Client, ctx context.Context, logger *logging.AppLogger, - objName objectType, objKey string, index int64, item T) error { + objName objectType, objKey string, index int64, item T, +) error { redisCmd := redisClient.LSet(ctx, objKey, index, item) if redisCmd.Err() != nil { logger.Warn(sf.Format("An error occurred during setting (update) item in LIST with key: \"{0}\" of type \"{1}\" with index {2}, error: {3}", diff --git a/managers/redis/manager_client_operations.go b/managers/redis/manager_client_operations.go index 03b709e..48f16c6 100644 --- a/managers/redis/manager_client_operations.go +++ b/managers/redis/manager_client_operations.go @@ -3,6 +3,7 @@ package redis import ( "encoding/json" "errors" + "github.com/wissance/Ferrum/config" "github.com/wissance/Ferrum/data" errors2 "github.com/wissance/Ferrum/errors" @@ -86,7 +87,7 @@ func (mn *RedisDataManager) CreateClient(realmName string, clientNew data.Client // TODO(SIA) use function isExists _, err = mn.GetClient(realmName, clientNew.Name) if err == nil { - return errors2.NewObjectExistsError(Client, clientNew.Name, sf.Format("realm: {0}", realmName)) + return errors2.NewObjectExistsError(string(Client), clientNew.Name, sf.Format("realm: {0}", realmName)) } if !errors.As(err, &errors2.ObjectNotFoundError{}) { return err @@ -207,6 +208,7 @@ func (mn *RedisDataManager) getRealmClients(realmName string) ([]data.ExtendedId * - clientName * Returns: *ExtendedIdentifier, error */ +// nolint unused func (mn *RedisDataManager) getRealmClient(realmName string, clientName string) (*data.ExtendedIdentifier, error) { realmClients, err := mn.getRealmClients(realmName) if err != nil { @@ -282,7 +284,7 @@ func (mn *RedisDataManager) createRealmClients(realmName string, realmClients [] if isAllPreDelete { if delErr := mn.deleteRealmClientsObject(realmName); delErr != nil { // todo(UMV): errors.Is because ErrZeroLength doesn't have custom type - if delErr != nil && !errors.Is(delErr, errors2.ErrNotExists) { + if !errors.Is(delErr, errors2.ErrNotExists) { return errors2.NewUnknownError("deleteRealmClientsObject", "RedisDataManager.createRealmClients", delErr) } } @@ -341,7 +343,7 @@ func (mn *RedisDataManager) deleteClientFromRealm(realmName string, clientName s } } if !isHasClient { - return errors2.NewObjectNotFoundError(Client, clientName, sf.Format("realm: {0}", realmName)) + return errors2.NewObjectNotFoundError(string(Client), clientName, sf.Format("realm: {0}", realmName)) } if createClientErr := mn.createRealmClients(realmName, realmClients, true); createClientErr != nil { return errors2.NewUnknownError("createRealmClients", "RedisDataManager.deleteClientFromRealm", createClientErr) diff --git a/managers/redis/manager_test.go b/managers/redis/manager_test.go index 838a79f..ae535cf 100644 --- a/managers/redis/manager_test.go +++ b/managers/redis/manager_test.go @@ -54,7 +54,7 @@ func TestCreateRealmSuccessfully(t *testing.T) { Value: uuid.New().String(), }, } - realm.Clients = append([]data.Client{client}) + realm.Clients = append(realm.Clients, client) } for _, u := range tCase.users { @@ -62,7 +62,7 @@ func TestCreateRealmSuccessfully(t *testing.T) { var rawUser interface{} err := json.Unmarshal([]byte(userJson), &rawUser) assert.NoError(t, err) - realm.Users = append([]interface{}{rawUser}) + realm.Users = append(realm.Users, rawUser) } err := manager.CreateRealm(realm) @@ -125,8 +125,10 @@ func TestCreateRealmWithFederationSuccessfully(t *testing.T) { err := manager.CreateRealm(realm) assert.NoError(t, err) r, err := manager.GetRealm(realm.Name) + assert.NoError(t, err) checkRealm(t, &realm, r) err = manager.DeleteRealm(realm.Name) + assert.NoError(t, err) userFederationConfigs, err := manager.GetUserFederationConfigs(realm.Name) assert.ErrorIs(t, err, appErrs.ErrZeroLength) assert.Nil(t, userFederationConfigs) @@ -179,13 +181,13 @@ func TestUpdateRealmSuccessfully(t *testing.T) { Value: uuid.New().String(), }, } - realm.Clients = append([]data.Client{client}) + realm.Clients = append(realm.Clients, client) userJson := sf.Format(`{"info":{"preferred_username":"{0}"}}`, "new_app_user") var rawUser interface{} err = json.Unmarshal([]byte(userJson), &rawUser) assert.NoError(t, err) - realm.Users = append([]interface{}{rawUser}) + realm.Users = append(realm.Users, rawUser) err = manager.UpdateRealm(prevRealmName, realm) assert.NoError(t, err) @@ -855,6 +857,7 @@ func TestCreateUserFederationServiceConfigSuccessfully(t *testing.T) { err := manager.CreateRealm(realm) assert.NoError(t, err) r, err := manager.GetRealm(realm.Name) + assert.NoError(t, err) checkRealm(t, &realm, r) // Creation of sample UserFederationService @@ -901,6 +904,7 @@ func TestUpdateUserFederationServiceConfigSuccessfully(t *testing.T) { err := manager.CreateRealm(realm) assert.NoError(t, err) r, err := manager.GetRealm(realm.Name) + assert.NoError(t, err) checkRealm(t, &realm, r) // Creation of sample UserFederationService @@ -954,6 +958,7 @@ func TestDeleteUserFederationServiceConfigSuccessfully(t *testing.T) { err := manager.CreateRealm(realm) assert.NoError(t, err) r, err := manager.GetRealm(realm.Name) + assert.NoError(t, err) checkRealm(t, &realm, r) // Creation of sample UserFederationService diff --git a/managers/redis/manager_user_federation_service_operations.go b/managers/redis/manager_user_federation_service_operations.go index 243860c..b22ef2c 100644 --- a/managers/redis/manager_user_federation_service_operations.go +++ b/managers/redis/manager_user_federation_service_operations.go @@ -3,6 +3,7 @@ package redis import ( "encoding/json" "errors" + "github.com/wissance/Ferrum/config" "github.com/wissance/Ferrum/data" appErrs "github.com/wissance/Ferrum/errors" @@ -69,7 +70,7 @@ func (mn *RedisDataManager) CreateUserFederationConfig(realmName string, userFed // TODO(UMV): use function isExists cfg, err := mn.GetUserFederationConfig(realmName, userFederationConfig.Name) if cfg != nil { - return appErrs.NewObjectExistsError(RealmUserFederationConfig, userFederationConfig.Name, sf.Format("realm: {0}", realmName)) + return appErrs.NewObjectExistsError(string(RealmUserFederationConfig), userFederationConfig.Name, sf.Format("realm: {0}", realmName)) } if !errors.As(err, &appErrs.ObjectNotFoundError{}) { return err @@ -202,7 +203,7 @@ func (mn *RedisDataManager) updateUserFederationConfigObject(realmName string, u } } - return appErrs.NewObjectNotFoundError(RealmUserFederationConfig, userFederationName, sf.Format("Realm: {0}", realmName)) + return appErrs.NewObjectNotFoundError(string(RealmUserFederationConfig), userFederationName, sf.Format("Realm: {0}", realmName)) } // deleteUserFederationConfigObject - deleting a data.UserFederationServiceConfig diff --git a/managers/redis/manager_user_operations.go b/managers/redis/manager_user_operations.go index ac4dbbd..a2520d2 100644 --- a/managers/redis/manager_user_operations.go +++ b/managers/redis/manager_user_operations.go @@ -138,7 +138,7 @@ func (mn *RedisDataManager) CreateUser(realmName string, userNew data.User) erro // TODO(SIA) use function isExists _, err = mn.GetUser(realmName, userName) if err == nil { - return errors2.NewObjectExistsError(User, userName, sf.Format("realm: {0}", realmName)) + return errors2.NewObjectExistsError(string(User), userName, sf.Format("realm: {0}", realmName)) } if !errors.As(err, &errors2.EmptyNotFoundErr) { mn.logger.Warn(sf.Format("CreateUser: GetUser failed, error: {0}", err.Error())) @@ -280,6 +280,7 @@ func (mn *RedisDataManager) getRealmUsers(realmName string) ([]data.ExtendedIden * - userName * Returns: *ExtendedIdentifier, error */ +// nolint unused func (mn *RedisDataManager) getRealmUser(realmName string, userName string) (*data.ExtendedIdentifier, error) { realmUsers, err := mn.getRealmUsers(realmName) if err != nil { @@ -297,7 +298,7 @@ func (mn *RedisDataManager) getRealmUser(realmName string, userName string) (*da } if !userFound { mn.logger.Debug(sf.Format("User with name: \"{0}\" was not found for realm: \"{1}\"", userName, realmName)) - return nil, errors2.NewObjectNotFoundError(User, userName, sf.Format("realm: {0}", realmName)) + return nil, errors2.NewObjectNotFoundError(string(User), userName, sf.Format("realm: {0}", realmName)) } return &user, nil } @@ -316,7 +317,7 @@ func (mn *RedisDataManager) getRealmUserById(realmName string, userId uuid.UUID) return nil, err } if errors.Is(err, errors2.ErrZeroLength) { - return nil, errors2.NewObjectNotFoundError(User, userId.String(), sf.Format("realm: {0}", realmName)) + return nil, errors2.NewObjectNotFoundError(string(User), userId.String(), sf.Format("realm: {0}", realmName)) } return nil, errors2.NewUnknownError("getRealmUsers", "RedisDataManager.getRealmUserById", err) } @@ -331,7 +332,7 @@ func (mn *RedisDataManager) getRealmUserById(realmName string, userId uuid.UUID) } if !userFound { mn.logger.Debug(sf.Format("User with id: \"{0}\" was not found for realm: \"{1}\"", userId, realmName)) - return nil, errors2.NewObjectNotFoundError(User, userId.String(), sf.Format("realm: {0}", realmName)) + return nil, errors2.NewObjectNotFoundError(string(User), userId.String(), sf.Format("realm: {0}", realmName)) } return &user, nil } @@ -392,7 +393,7 @@ func (mn *RedisDataManager) createRealmUsers(realmName string, realmUsers []data if isAllPreDelete { if deleteRealmUserErr := mn.deleteRealmUsersObject(realmName); deleteRealmUserErr != nil { // todo(UMV): errors.Is because ErrNotExists doesn't have custom type - if deleteRealmUserErr != nil && !errors.Is(deleteRealmUserErr, errors2.ErrNotExists) { + if !errors.Is(deleteRealmUserErr, errors2.ErrNotExists) { return errors2.NewUnknownError("deleteRealmUsersObject", "RedisDataManager.createRealmUsers", deleteRealmUserErr) } } @@ -450,7 +451,7 @@ func (mn *RedisDataManager) deleteUserFromRealm(realmName string, userName strin } } if !isHasUser { - return errors2.NewObjectNotFoundError(User, userName, sf.Format("realm: {0}", realmName)) + return errors2.NewObjectNotFoundError(string(User), userName, sf.Format("realm: {0}", realmName)) } if createRealmUserErr := mn.createRealmUsers(realmName, realmUsers, true); createRealmUserErr != nil { return errors2.NewUnknownError("createRealmUsers", "RedisDataManager.deleteUserFromRealm", createRealmUserErr) diff --git a/services/federation/ldap_federation_service.go b/services/federation/ldap_federation_service.go index b75c5c0..fee1faa 100644 --- a/services/federation/ldap_federation_service.go +++ b/services/federation/ldap_federation_service.go @@ -2,6 +2,7 @@ package federation import ( "errors" + "github.com/go-ldap/ldap/v3" "github.com/wissance/Ferrum/data" appErrs "github.com/wissance/Ferrum/errors" @@ -62,7 +63,7 @@ func (s *LdapUserFederation) GetUser(userName string, mask string) (data.User, e } if result != nil { - if result.Entries == nil || len(result.Entries) == 0 { + if len(result.Entries) == 0 { return nil, appErrs.NewFederatedUserNotFound(string(s.config.Type), s.config.Name, s.config.Url, userName) } @@ -72,7 +73,7 @@ func (s *LdapUserFederation) GetUser(userName string, mask string) (data.User, e } // todo(UMV): convert []Attributes to Json and pass - //result.Entries[0].Attributes[0].Name + // result.Entries[0].Attributes[0].Name return nil, nil } @@ -86,5 +87,4 @@ func (s *LdapUserFederation) Authenticate(userName string, password string) (boo } func (s *LdapUserFederation) Init() { - }