diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 21ab17a..c96d13a 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -8,8 +8,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable - name: Lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v6 with: - version: v1.53.3 \ No newline at end of file + version: v1.58 diff --git a/Dockerfile b/Dockerfile index 6cb07f0..a46eb76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,7 @@ RUN apk upgrade COPY --from=build /app ./ COPY conf/*.sql ./conf/ +COPY html/*.html ./html/ COPY openapi.yaml ./ COPY metadata.json ./ diff --git a/README.md b/README.md index 1b79241..f6e0765 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ This initialization can be handled by the `reset.sql` script. - `LOG_LEVEL`(optional): defines the minimum level that should be [logged](https://github.com/eliona-smart-building-assistant/go-utils/blob/main/log/README.md). The default level is `info`. -- `SSO_SERVER_PORT` (optional): defines the port for Single Sign On Services, here SAML 2.0. The default value is Port `8081`. +- `SSO_SERVER_PORT` (optional): defines the port for Single Sign On Services, here SAML 2.0. The default value is Port `8081`. MUST provide unauthenticated and unauthorized access. ### Database tables ### diff --git a/apiserver/README.md b/apiserver/README.md index 8b14238..ce4cef1 100644 --- a/apiserver/README.md +++ b/apiserver/README.md @@ -13,7 +13,7 @@ To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 1.0.0 -- Build date: 2024-02-02T12:01:58.629258516Z[Etc/UTC] +- Build date: 2024-02-13T18:20:07.718913864Z[Etc/UTC] ### Running the server diff --git a/apiserver/model_permissions.go b/apiserver/model_permissions.go index 73f059a..6237d53 100644 --- a/apiserver/model_permissions.go +++ b/apiserver/model_permissions.go @@ -15,17 +15,23 @@ type Permissions struct { // Configuration Id refer to config's id. Can only be 1 Id int32 `json:"id,omitempty"` - DefaultSystemRole string `json:"default_system_role,omitempty"` + DefaultSystemRole string `json:"defaultSystemRole,omitempty"` - DefaultProjRole string `json:"default_proj_role,omitempty"` + DefaultProjRole string `json:"defaultProjRole,omitempty"` - SystemRoleSamlAttribute *string `json:"system_role_saml_attribute,omitempty"` + DefaultLanguage string `json:"defaultLanguage,omitempty"` - SystemRoleMap *[]RoleMap `json:"system_role_map,omitempty"` + SystemRoleSamlAttribute *string `json:"systemRoleSamlAttribute,omitempty"` - ProjRoleSamlAttribute *string `json:"proj_role_saml_attribute,omitempty"` + SystemRoleMap *[]RoleMap `json:"systemRoleMap,omitempty"` - ProjRoleMap *[]RoleMap `json:"proj_role_map,omitempty"` + ProjRoleSamlAttribute *string `json:"projRoleSamlAttribute,omitempty"` + + ProjRoleMap *[]RoleMap `json:"projRoleMap,omitempty"` + + LanguageSamlAttribute *string `json:"languageSamlAttribute,omitempty"` + + LanguageMap *[]RoleMap `json:"languageMap,omitempty"` } // AssertPermissionsRequired checks if the required fields are not zero-ed @@ -44,6 +50,13 @@ func AssertPermissionsRequired(obj Permissions) error { } } } + if obj.LanguageMap != nil { + for _, el := range *obj.LanguageMap { + if err := AssertRoleMapRequired(el); err != nil { + return err + } + } + } return nil } diff --git a/app.go b/app.go index 58a186c..ce41566 100644 --- a/app.go +++ b/app.go @@ -36,8 +36,6 @@ const ( LOG_REGIO = "app" API_SERVER_PORT = 3000 SSO_SERVER_PORT = 8081 // Publicly accessible without auth. See wiki. - - SAML_SPECIFIC_ENDPOINT_PATH = "/saml/" ) func initialize() { @@ -86,7 +84,7 @@ func run() { } else if config.IdpMetadataXml != nil { metadata = []byte(*config.IdpMetadataXml) } else { - log.Error(LOG_REGIO, "not able to set IdP Metadata") + log.Warn(LOG_REGIO, "not able to set IdP Metadata. PLS setup the IdP Metadata in config!") } apiPort := common.Getenv("API_SERVER_PORT", strconv.Itoa(API_SERVER_PORT)) @@ -104,6 +102,7 @@ func run() { &config.SignedRequest, &config.ForceAuthn, &config.CookieSecure, + saml.PUBLIC_BASE_PATH, ) if err != nil { log.Fatal(LOG_REGIO, "cannot initialize saml service provider: %v", err) @@ -122,6 +121,7 @@ func run() { ) go func() { + log.Info(LOG_REGIO, "api server started @ %v", apiPort) err := http.ListenAndServe(":"+apiPort, router) if err != nil { log.Fatal(LOG_REGIO, "app api server: %v", err) @@ -129,22 +129,19 @@ func run() { }() // saml specific handle (no RESTful) to router - elionaAuth := eliona.NewSingleSignOn(config.OwnUrl, + sso := eliona.NewSingleSignOn(config.OwnUrl, config.UserToArchive, config.LoginFailedUrl) - activeHandleFunc := http.HandlerFunc(elionaAuth.ActiveHandle) + activeHandleFunc := http.HandlerFunc(sso.ActiveHandle) http.Handle(eliona.ENDPOINT_SSO_GENERIC_ACTIVE, activeHandleFunc) - authHandleFunc := http.HandlerFunc(elionaAuth.Authentication) // TODO: Not completely implemented. + samlErrHandleFunc := http.HandlerFunc(sso.DefaultLoginError) + http.Handle(eliona.ENDPOINT_SSO_GENERIC_ERROR, samlErrHandleFunc) + authHandleFunc := http.HandlerFunc(sso.Authentication) http.Handle(eliona.ENDPOINT_SSO_GENERIC_VERIFICATION, - sp.GetMiddleWare().RequireAccount(authHandleFunc)) - http.Handle(SAML_SPECIFIC_ENDPOINT_PATH, sp.GetMiddleWare()) - - // for backwards compatibility, can be removed when the frontend is reworked to the new generic /sso/* endpoints - http.Handle("/adfs/active/", activeHandleFunc) - http.Handle("/adfs/auth/", - sp.GetMiddleWare().RequireAccount(authHandleFunc)) + sp.FixPath((sp.GetMiddleWare().RequireAccount(authHandleFunc)))) + http.Handle(saml.SP_HANDLE_BASE_PATH, sp.FixPath(sp.GetMiddleWare())) - log.Info(LOG_REGIO, "started @ %v", samlSpPort) + log.Info(LOG_REGIO, "public http server started @ %v", samlSpPort) err = http.ListenAndServe(":"+samlSpPort, nil) if err != nil { log.Error("sp app", "exiting due to an error: %v", err) diff --git a/appdb/attribute_map.go b/appdb/attribute_map.go index 4e86043..d4c1c59 100644 --- a/appdb/attribute_map.go +++ b/appdb/attribute_map.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.16.1 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package appdb diff --git a/appdb/boil_queries.go b/appdb/boil_queries.go index 0d64354..b3bd11d 100644 --- a/appdb/boil_queries.go +++ b/appdb/boil_queries.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.16.1 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package appdb diff --git a/appdb/boil_table_names.go b/appdb/boil_table_names.go index 4348fdf..0bf3381 100644 --- a/appdb/boil_table_names.go +++ b/appdb/boil_table_names.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.16.1 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package appdb diff --git a/appdb/boil_types.go b/appdb/boil_types.go index b2f6cc4..85d3602 100644 --- a/appdb/boil_types.go +++ b/appdb/boil_types.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.16.1 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package appdb diff --git a/appdb/boil_view_names.go b/appdb/boil_view_names.go index 7c2ce91..f407775 100644 --- a/appdb/boil_view_names.go +++ b/appdb/boil_view_names.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.16.1 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package appdb diff --git a/appdb/config.go b/appdb/config.go index 43fc41c..7374262 100644 --- a/appdb/config.go +++ b/appdb/config.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.16.1 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package appdb diff --git a/appdb/permissions.go b/appdb/permissions.go index b389814..dd4ead8 100644 --- a/appdb/permissions.go +++ b/appdb/permissions.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.16.1 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package appdb @@ -27,10 +27,13 @@ type Permission struct { ID int32 `boil:"id" json:"id" toml:"id" yaml:"id"` DefaultSystemRole string `boil:"default_system_role" json:"default_system_role" toml:"default_system_role" yaml:"default_system_role"` DefaultProjRole string `boil:"default_proj_role" json:"default_proj_role" toml:"default_proj_role" yaml:"default_proj_role"` + DefaultLanguage string `boil:"default_language" json:"default_language" toml:"default_language" yaml:"default_language"` SystemRoleSamlAttribute null.String `boil:"system_role_saml_attribute" json:"system_role_saml_attribute,omitempty" toml:"system_role_saml_attribute" yaml:"system_role_saml_attribute,omitempty"` SystemRoleMap null.JSON `boil:"system_role_map" json:"system_role_map,omitempty" toml:"system_role_map" yaml:"system_role_map,omitempty"` ProjRoleSamlAttribute null.String `boil:"proj_role_saml_attribute" json:"proj_role_saml_attribute,omitempty" toml:"proj_role_saml_attribute" yaml:"proj_role_saml_attribute,omitempty"` ProjRoleMap null.JSON `boil:"proj_role_map" json:"proj_role_map,omitempty" toml:"proj_role_map" yaml:"proj_role_map,omitempty"` + LanguageSamlAttribute null.String `boil:"language_saml_attribute" json:"language_saml_attribute,omitempty" toml:"language_saml_attribute" yaml:"language_saml_attribute,omitempty"` + LanguageMap null.JSON `boil:"language_map" json:"language_map,omitempty" toml:"language_map" yaml:"language_map,omitempty"` R *permissionR `boil:"-" json:"-" toml:"-" yaml:"-"` L permissionL `boil:"-" json:"-" toml:"-" yaml:"-"` @@ -40,36 +43,48 @@ var PermissionColumns = struct { ID string DefaultSystemRole string DefaultProjRole string + DefaultLanguage string SystemRoleSamlAttribute string SystemRoleMap string ProjRoleSamlAttribute string ProjRoleMap string + LanguageSamlAttribute string + LanguageMap string }{ ID: "id", DefaultSystemRole: "default_system_role", DefaultProjRole: "default_proj_role", + DefaultLanguage: "default_language", SystemRoleSamlAttribute: "system_role_saml_attribute", SystemRoleMap: "system_role_map", ProjRoleSamlAttribute: "proj_role_saml_attribute", ProjRoleMap: "proj_role_map", + LanguageSamlAttribute: "language_saml_attribute", + LanguageMap: "language_map", } var PermissionTableColumns = struct { ID string DefaultSystemRole string DefaultProjRole string + DefaultLanguage string SystemRoleSamlAttribute string SystemRoleMap string ProjRoleSamlAttribute string ProjRoleMap string + LanguageSamlAttribute string + LanguageMap string }{ ID: "permissions.id", DefaultSystemRole: "permissions.default_system_role", DefaultProjRole: "permissions.default_proj_role", + DefaultLanguage: "permissions.default_language", SystemRoleSamlAttribute: "permissions.system_role_saml_attribute", SystemRoleMap: "permissions.system_role_map", ProjRoleSamlAttribute: "permissions.proj_role_saml_attribute", ProjRoleMap: "permissions.proj_role_map", + LanguageSamlAttribute: "permissions.language_saml_attribute", + LanguageMap: "permissions.language_map", } // Generated where @@ -102,18 +117,24 @@ var PermissionWhere = struct { ID whereHelperint32 DefaultSystemRole whereHelperstring DefaultProjRole whereHelperstring + DefaultLanguage whereHelperstring SystemRoleSamlAttribute whereHelpernull_String SystemRoleMap whereHelpernull_JSON ProjRoleSamlAttribute whereHelpernull_String ProjRoleMap whereHelpernull_JSON + LanguageSamlAttribute whereHelpernull_String + LanguageMap whereHelpernull_JSON }{ ID: whereHelperint32{field: "\"saml_sp\".\"permissions\".\"id\""}, DefaultSystemRole: whereHelperstring{field: "\"saml_sp\".\"permissions\".\"default_system_role\""}, DefaultProjRole: whereHelperstring{field: "\"saml_sp\".\"permissions\".\"default_proj_role\""}, + DefaultLanguage: whereHelperstring{field: "\"saml_sp\".\"permissions\".\"default_language\""}, SystemRoleSamlAttribute: whereHelpernull_String{field: "\"saml_sp\".\"permissions\".\"system_role_saml_attribute\""}, SystemRoleMap: whereHelpernull_JSON{field: "\"saml_sp\".\"permissions\".\"system_role_map\""}, ProjRoleSamlAttribute: whereHelpernull_String{field: "\"saml_sp\".\"permissions\".\"proj_role_saml_attribute\""}, ProjRoleMap: whereHelpernull_JSON{field: "\"saml_sp\".\"permissions\".\"proj_role_map\""}, + LanguageSamlAttribute: whereHelpernull_String{field: "\"saml_sp\".\"permissions\".\"language_saml_attribute\""}, + LanguageMap: whereHelpernull_JSON{field: "\"saml_sp\".\"permissions\".\"language_map\""}, } // PermissionRels is where relationship names are stored. @@ -144,9 +165,9 @@ func (r *permissionR) GetIDConfig() *Config { type permissionL struct{} var ( - permissionAllColumns = []string{"id", "default_system_role", "default_proj_role", "system_role_saml_attribute", "system_role_map", "proj_role_saml_attribute", "proj_role_map"} + permissionAllColumns = []string{"id", "default_system_role", "default_proj_role", "default_language", "system_role_saml_attribute", "system_role_map", "proj_role_saml_attribute", "proj_role_map", "language_saml_attribute", "language_map"} permissionColumnsWithoutDefault = []string{} - permissionColumnsWithDefault = []string{"id", "default_system_role", "default_proj_role", "system_role_saml_attribute", "system_role_map", "proj_role_saml_attribute", "proj_role_map"} + permissionColumnsWithDefault = []string{"id", "default_system_role", "default_proj_role", "default_language", "system_role_saml_attribute", "system_role_map", "proj_role_saml_attribute", "proj_role_map", "language_saml_attribute", "language_map"} permissionPrimaryKeyColumns = []string{"id"} permissionGeneratedColumns = []string{} ) diff --git a/appdb/psql_upsert.go b/appdb/psql_upsert.go index 4482228..efe1a8a 100644 --- a/appdb/psql_upsert.go +++ b/appdb/psql_upsert.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.16.1 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package appdb diff --git a/conf/converter.go b/conf/converter.go index b672c98..bc3e71e 100644 --- a/conf/converter.go +++ b/conf/converter.go @@ -19,7 +19,9 @@ import ( "encoding/json" "saml-sso/apiserver" "saml-sso/appdb" + "strconv" + "github.com/eliona-smart-building-assistant/go-utils/log" "github.com/volatiletech/null/v8" ) @@ -92,6 +94,11 @@ func PermissionApiToDbForm(permissions *apiserver.Permissions) (*appdb.Permissio return nil, err } + langMap, err := RoleMapToNullableJSON(permissions.LanguageMap) + if err != nil { + return nil, err + } + return &appdb.Permission{ ID: permissions.Id, DefaultSystemRole: permissions.DefaultSystemRole, @@ -100,6 +107,9 @@ func PermissionApiToDbForm(permissions *apiserver.Permissions) (*appdb.Permissio SystemRoleMap: sysRoleMap, ProjRoleSamlAttribute: null.StringFromPtr(permissions.ProjRoleSamlAttribute), ProjRoleMap: projRoleMap, + DefaultLanguage: permissions.DefaultLanguage, + LanguageSamlAttribute: null.StringFromPtr(permissions.LanguageSamlAttribute), + LanguageMap: langMap, }, nil } @@ -108,15 +118,21 @@ func PermissionDbToApiForm(permission *appdb.Permission) (*apiserver.Permissions var ( systemRoleMap *[]apiserver.RoleMap projRoleMap *[]apiserver.RoleMap + langMap *[]apiserver.RoleMap err error ) - systemRoleMap, err = NullableJSONToRoleMapPtr(permission.ProjRoleMap) + systemRoleMap, err = NullableJSONToRoleMapPtr(permission.SystemRoleMap) if err != nil { return nil, err } - projRoleMap, err = NullableJSONToRoleMapPtr(permission.SystemRoleMap) + projRoleMap, err = NullableJSONToRoleMapPtr(permission.ProjRoleMap) + if err != nil { + return nil, err + } + + langMap, err = NullableJSONToRoleMapPtr(permission.LanguageMap) if err != nil { return nil, err } @@ -129,32 +145,120 @@ func PermissionDbToApiForm(permission *appdb.Permission) (*apiserver.Permissions SystemRoleMap: systemRoleMap, ProjRoleSamlAttribute: permission.ProjRoleSamlAttribute.Ptr(), ProjRoleMap: projRoleMap, + DefaultLanguage: permission.DefaultLanguage, + LanguageSamlAttribute: permission.LanguageSamlAttribute.Ptr(), + LanguageMap: langMap, }, nil } +// convert [{"ElionaRole":"roleIdOrRoleName", "SamlValue":"samlValue"}] to {"samlValue":"roleIdOrRoleName"} +// to use it as map[string]any func RoleMapToNullableJSON(roleMapPtr *[]apiserver.RoleMap) (null.JSON, error) { var ( jsonBytes []byte err error + + roleMap map[string]any ) if roleMapPtr == nil { return null.JSONFromPtr(nil), nil } - jsonBytes, err = json.Marshal(*roleMapPtr) + + roleMap = ApiRoleMapToGolangMap(*roleMapPtr) + + jsonBytes, err = json.Marshal(roleMap) return null.JSONFrom(jsonBytes), err } func NullableJSONToRoleMapPtr(nullableJson null.JSON) (*[]apiserver.RoleMap, error) { var ( - roleMap []apiserver.RoleMap = []apiserver.RoleMap{} - err error + roleMapApi []apiserver.RoleMap + roleMapDb map[string]any + err error ) if nullableJson.Ptr() == nil { return nil, nil } - err = json.Unmarshal(nullableJson.JSON, &roleMap) - return &roleMap, err + err = json.Unmarshal(nullableJson.JSON, &roleMapDb) + + roleMapApi = DBRoleToApiRole(roleMapDb) + + return &roleMapApi, err +} + +func ApiRoleMapToGolangMap(roleMap []apiserver.RoleMap) (gRoleMap map[string]any) { + + gRoleMap = make(map[string]any) + + for _, m := range roleMap { + var elionaRole any = m.ElionaRole + + if i, err := strconv.Atoi(m.ElionaRole); err == nil { + elionaRole = i + } + + gRoleMap[m.SamlValue] = elionaRole + } + + return +} + +func DBRoleToApiRole(roleMap map[string]any) (apiRoleMap []apiserver.RoleMap) { + + apiRoleMap = make([]apiserver.RoleMap, 0) + + for samlValue, elionaRole := range roleMap { + var elionaRoleS string + + switch v := elionaRole.(type) { + case string: + elionaRoleS = v + case int: + elionaRoleS = strconv.Itoa(v) + case float64: + elionaRoleS = strconv.Itoa(int(v)) + default: + log.Warn(LOG_REGIO, "unknown type for eliona role: %T, %v", v, v) + } + + var singleApiMap apiserver.RoleMap = apiserver.RoleMap{ + ElionaRole: elionaRoleS, + SamlValue: samlValue, + } + + apiRoleMap = append(apiRoleMap, singleApiMap) + } + + return +} + +func AnyToRoleId(roleNameOrId any, aclRoles map[string]int) (roleId int) { + roleId = -1 + + switch v := roleNameOrId.(type) { + case string: + roleId = StringToRoleId(v, aclRoles) + case int: + roleId = v + case float64: + roleId = int(v) + default: + log.Warn(LOG_REGIO, "unknown type for roleNameOrId: %T, %v", v, v) + } + + return +} + +func StringToRoleId(roleNameOrId string, aclRoles map[string]int) (roleId int) { + var err error + + roleId, err = strconv.Atoi(roleNameOrId) + if err != nil { + roleId = aclRoles[roleNameOrId] + } + + return } diff --git a/conf/init.sql b/conf/init.sql index d86c691..7fd9fd6 100644 --- a/conf/init.sql +++ b/conf/init.sql @@ -1,5 +1,5 @@ -- This file is part of the eliona project. --- Copyright © 2023 Eliona by IoTEC AG. All Rights Reserved. +-- Copyright © 2024 Eliona by IoTEC AG. All Rights Reserved. -- ______ _ _ -- | ____| (_) -- | |__ | |_ ___ _ __ __ _ @@ -15,40 +15,44 @@ CREATE SCHEMA IF NOT EXISTS saml_sp ; -GRANT USAGE ON SCHEMA saml_sp TO leicom ; -GRANT ALL ON SCHEMA saml_sp TO leicom ; - +-- general settings for the SAML Service Provider (SP) CREATE TABLE IF NOT EXISTS saml_sp.config ( - id INT PRIMARY KEY NOT NULL DEFAULT 1 CHECK (id = 1) , -- due to the architecture of eliona only one configuration (1 sso per instance) is possible - enable BOOLEAN NOT NULL DEFAULT true , - sp_certificate TEXT NOT NULL , - sp_private_key TEXT NOT NULL , - idp_metadata_url TEXT , - metadata_xml TEXT DEFAULT NULL , - own_url TEXT NOT NULL , - user_to_archive BOOLEAN NOT NULL DEFAULT false , - allow_initialization_by_idp BOOLEAN NOT NULL DEFAULT false , - signed_request BOOLEAN NOT NULL DEFAULT true , - force_authn BOOLEAN NOT NULL DEFAULT false , - entity_id TEXT NOT NULL DEFAULT '{ownUrl}/saml/metadata', - cookie_secure BOOLEAN NOT NULL DEFAULT false , - login_failed_url TEXT NOT NULL DEFAULT '{ownUrl}/noLogin' + id INT PRIMARY KEY NOT NULL DEFAULT 1 CHECK (id = 1) , -- due to the architecture of eliona only one configuration (1 sso per instance) is possible + enable BOOLEAN NOT NULL DEFAULT true , + sp_certificate TEXT NOT NULL , -- own cert + sp_private_key TEXT NOT NULL , -- key to own cert + idp_metadata_url TEXT , -- url where IdP's metadata can fetched + metadata_xml TEXT DEFAULT NULL , -- if no url is avalable, insert metadata xml here + own_url TEXT NOT NULL , -- the own url e.g. https://my.eliona.xy + user_to_archive BOOLEAN NOT NULL DEFAULT false , -- put user to archive @ first login (do not allow login, if not verified by sys admin) + allow_initialization_by_idp BOOLEAN NOT NULL DEFAULT false , -- if the IdP can initialize the login (means, no SAML request was issued by our sp) + signed_request BOOLEAN NOT NULL DEFAULT true , -- sign the SAML request + force_authn BOOLEAN NOT NULL DEFAULT false , + entity_id TEXT NOT NULL DEFAULT '{ownUrl}/apps-public/saml-sso/saml/metadata', + cookie_secure BOOLEAN NOT NULL DEFAULT false , + login_failed_url TEXT NOT NULL DEFAULT '{ownUrl}/noLogin' -- redirect url when a user login fails ) ; +-- general settings for adding a user CREATE TABLE IF NOT EXISTS saml_sp.attribute_map ( -- SAML session attribute names. id INT PRIMARY KEY NOT NULL DEFAULT 1 REFERENCES saml_sp.config(id) ON UPDATE CASCADE , - email TEXT NOT NULL DEFAULT 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn', + email TEXT NOT NULL DEFAULT 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn', -- SAML attribute email and login first_name TEXT DEFAULT NULL , last_name TEXT DEFAULT NULL , phone TEXT DEFAULT NULL ) ; +-- settings for define users permissions CREATE TABLE IF NOT EXISTS saml_sp.permissions ( id INT PRIMARY KEY NOT NULL DEFAULT 1 REFERENCES saml_sp.config(id) ON UPDATE CASCADE, - default_system_role TEXT NOT NULL DEFAULT 'System user' , -- reference to is maybe a bad idea (due to the new ACL) - default_proj_role TEXT NOT NULL DEFAULT 'Project user' , - system_role_saml_attribute TEXT , - system_role_map JSON , - proj_role_saml_attribute TEXT , - proj_role_map JSON + default_system_role TEXT NOT NULL DEFAULT 'System user' , -- reference to is maybe a bad idea (due to the new ACL) + default_proj_role TEXT NOT NULL DEFAULT 'Project user' , -- can be the role display name or role id + default_language TEXT NOT NULL DEFAULT 'en' , -- see constraint + system_role_saml_attribute TEXT , -- attribute that contains the system roles which should be mapped + system_role_map JSON , -- e.g. {"company xy-Admin":"System admin", ...} + proj_role_saml_attribute TEXT , -- attribute that contains the project roles which should be mapped + proj_role_map JSON , -- e.g. {"company xy-Employee":"Project user", ...} + language_saml_attribute TEXT , -- attribute that contains the users language which should be mapped + language_map JSON , -- e.g. {"Sprache:Deutsch":"de", "Sprache:Englisch":"en"} + CONSTRAINT chk_language CHECK (default_language IN ('en', 'de', 'it', 'fr')) ) ; diff --git a/eliona/others.go b/eliona/others.go index b094755..63d7aa2 100644 --- a/eliona/others.go +++ b/eliona/others.go @@ -20,6 +20,7 @@ package eliona import ( "database/sql" "errors" + "fmt" "strings" "github.com/eliona-smart-building-assistant/go-eliona/app" @@ -119,14 +120,35 @@ func GetFirstProjectId() (projectId string, err error) { return } -func GetRoleIdByDisplayName(displayName string) (roleId int, err error) { - row := getDb().QueryRow("SELECT role_id FROM acl_role WHERE displayname = $1", displayName) +func GetACLRoleMap() (roleMap map[string]int, err error) { - if err = row.Err(); err != nil { + roleMap = make(map[string]int) + + rows, err := getDb().Query("SELECT role_id, displayname FROM acl_role") + + if err != nil { + err = fmt.Errorf("cannot get acl roles: %v", err) return } - err = row.Scan(&roleId) + for rows.Next() { + var ( + roleId int + roleDispName string + ) + err = rows.Scan(&roleId, &roleDispName) + if err != nil { + return + } + roleMap[roleDispName] = roleId + } + + return +} + +func SetUserPermissions(userId *string, systemRoleId int, language string) (err error) { + _, err = getDb().Exec("UPDATE public.eliona_user SET role_id = $1, language = $2 WHERE user_id = $3", + systemRoleId, language, userId) return } diff --git a/eliona/single_sign_on.go b/eliona/single_sign_on.go index 2443d1a..ef66e32 100644 --- a/eliona/single_sign_on.go +++ b/eliona/single_sign_on.go @@ -18,8 +18,11 @@ package eliona import ( "context" "encoding/json" + "errors" "fmt" "net/http" + "net/url" + "os" "saml-sso/apiserver" "saml-sso/conf" "saml-sso/utils" @@ -31,16 +34,20 @@ import ( const ( LOG_REGIO = "eliona" + + DefaultLang = "en" ) const ( - ENDPOINT_SSO_GENERIC_VERIFICATION = "/sso/auth" - ENDPOINT_SSO_GENERIC_ACTIVE = "/sso/active" + ENDPOINT_SSO_GENERIC_VERIFICATION = "/auth" + ENDPOINT_SSO_GENERIC_ACTIVE = "/active" + ENDPOINT_SSO_GENERIC_ERROR = "/error.html" ) type SingleSignOn struct { baseUrl string redirectNoLogin string + htmlContent bool userToArchive bool eliApi *EliApiV2 } @@ -48,12 +55,16 @@ type SingleSignOn struct { func NewSingleSignOn(baseUrl string, userToArchive bool, redirectNoLogin string) *SingleSignOn { - return &SingleSignOn{ - baseUrl: baseUrl, - userToArchive: userToArchive, - eliApi: NewEliApiV2(), - redirectNoLogin: utils.SubstituteOwnUrlUrlString(redirectNoLogin, baseUrl), + sso := &SingleSignOn{ + baseUrl: baseUrl, + userToArchive: userToArchive, + eliApi: NewEliApiV2(), } + + sso.redirectNoLogin, sso.htmlContent = + utils.SubstituteOwnUrlUrlString(redirectNoLogin, baseUrl) + + return sso } func (s *SingleSignOn) ActiveHandle(w http.ResponseWriter, r *http.Request) { @@ -88,6 +99,18 @@ func (s *SingleSignOn) ActiveHandle(w http.ResponseWriter, r *http.Request) { } } +func (s *SingleSignOn) DefaultLoginError(w http.ResponseWriter, r *http.Request) { + content, err := os.ReadFile("html/error.html") + if err != nil { + log.Error(LOG_REGIO, "read def err page: %v", err) + } + w.WriteHeader(http.StatusOK) + _, err = w.Write(content) + if err != nil { + log.Error(LOG_REGIO, "send def err page: %v", err) + } +} + func (s *SingleSignOn) Authentication(w http.ResponseWriter, r *http.Request) { log.Info(LOG_REGIO, "authentication handle called [%s]", r.Method) @@ -100,11 +123,8 @@ func (s *SingleSignOn) Authentication(w http.ResponseWriter, r *http.Request) { firstname, lastname string phone *string - user *api.User - jwt *string - setCookies http.Cookie - - errorMessage []byte + user *api.User + jwt *string ) // Try to obtain real user IP. @@ -112,13 +132,15 @@ func (s *SingleSignOn) Authentication(w http.ResponseWriter, r *http.Request) { if userIp == "" { userIp = r.Header.Get("X-Real-Ip") } + if userIp == "" { + userIp = r.RemoteAddr + } log.Debug(LOG_REGIO, "user from %s called authentication ep", userIp) mapping, err = conf.GetAttributeMapping(context.Background()) if err != nil { - log.Error(LOG_REGIO, "cannot get attribute mapping. skip auth. %v", err) - errorMessage = []byte(err.Error()) - goto internalServerError + s.authFailed(true, loginEmail, userIp, fmt.Sprintf("cannot get attribute mapping: %v", err), w, r) + return } if mapping.Email != "" { @@ -141,13 +163,33 @@ func (s *SingleSignOn) Authentication(w http.ResponseWriter, r *http.Request) { } log.Info(LOG_REGIO, "User with firstname: %v, lastname: %v, email/login: "+ - "%v, phone: %v want to login", - firstname, lastname, loginEmail, phone) + "%v, phone: %v from %v want to login", + firstname, lastname, loginEmail, phone, userIp) // get or create user user, err = s.eliApi.GetUserIfExists(loginEmail) if err != nil { log.Info(LOG_REGIO, "user doesn't exist. creating now user...") + + projectId, err := s.getProjectId() + if err != nil { + s.authFailed(true, loginEmail, userIp, fmt.Sprintf("cannot obtain project id: %v", err), w, r) + return + } + + sysRoleId, projRoleId, lang, err := s.getPermissionsAndLang(r.Context()) + if err != nil { + log.Info(LOG_REGIO, "mapping failed: sysRoleId:%v, projRoleId:%v, lang:%v err:%v", + sysRoleId, projRoleId, lang, err) + // a wrong, uncomplete mapping is not a internal error + // maybe even wanted for some user groups + s.authFailed(false, loginEmail, userIp, fmt.Sprintf("cannot map saml attributes: %v", err), w, r) + return + } + + log.Debug(LOG_REGIO, "assigned sysRole: %d, projRole: %d, lang: %s", sysRoleId, projRoleId, lang) + + // cannot set role over api user, err = s.eliApi.AddUser(&api.User{ Email: loginEmail, Firstname: *api.NewNullableString(&firstname), @@ -156,104 +198,206 @@ func (s *SingleSignOn) Authentication(w http.ResponseWriter, r *http.Request) { // Archived: a.userToArchive, // not possible over APIv2 }) if err != nil { - log.Error(LOG_REGIO, "creating user: %v", err) - errorMessage = []byte(err.Error()) - goto internalServerError + s.authFailed(true, loginEmail, userIp, fmt.Sprintf("cannot add user: %v", err), w, r) + return } err = UpdateElionaUserArchivedPhone(user.Email, phone, s.userToArchive) if err != nil { - log.Error(LOG_REGIO, "cannot set phone and archive flag: %v", err) - errorMessage = []byte(err.Error()) - goto internalServerError + s.authFailed(true, loginEmail, userIp, fmt.Sprintf("failed to set phone number and archived flag: %v", err), w, r) + return } - err = s.setUserPermissions(user.Id.Get()) + err = SetUserPermissions(user.Id.Get(), sysRoleId, lang) + if err != nil { + s.authFailed(true, loginEmail, userIp, fmt.Sprintf("cannot set system user cnf: %v", err), w, r) + return + } + err = SetProjectUser(projectId, user.Id.Get(), projRoleId) if err != nil { - log.Error(LOG_REGIO, "cannot set user permissions: %v", err) - goto notAuthenticated + s.authFailed(true, loginEmail, userIp, fmt.Sprintf("cannot set project user cnf: %v", err), w, r) + return } } // obtain a jwt to login via cookies jwt, err = GetElionaJsonWebToken(user.Email) if err != nil { - log.Error(LOG_REGIO, "cannot obtain a JWT") - errorMessage = []byte("cannot obtain a JWT") - goto internalServerError + s.authFailed(true, loginEmail, userIp, "cannot obtain a JWT", w, r) + return } log.Debug(LOG_REGIO, "User %s with token %v", user.Email, jwt) if jwt == nil || *jwt == "" { - goto notAuthenticated + s.authFailed(true, loginEmail, userIp, "invalid JWT returned", w, r) + return + } else { + s.authSuccessful(loginEmail, jwt, w, r) } +} - goto authenticated - -authenticated: - log.Info(LOG_REGIO, "authenticated user login: %s", loginEmail) - setCookies = http.Cookie{ +func (s *SingleSignOn) authSuccessful(login string, jwt *string, w http.ResponseWriter, r *http.Request) { + log.Info(LOG_REGIO, "authenticated user login: %s", login) + setCookies := http.Cookie{ Name: "elionaAuthorization", Value: *jwt, Path: "/"} http.SetCookie(w, &setCookies) http.Redirect(w, r, s.baseUrl, http.StatusFound) - return +} + +func (s *SingleSignOn) authFailed(intError bool, login string, ip string, errorMsg string, + w http.ResponseWriter, r *http.Request) { -notAuthenticated: log.Info(LOG_REGIO, "not authenticated user tried to login: %s, %s", - loginEmail, userIp) + login, ip) + + if intError { + log.Warn(LOG_REGIO, "internal server error occured: %s", errorMsg) + w.WriteHeader(http.StatusInternalServerError) + if _, err := w.Write([]byte(errorMsg)); err != nil { + log.Warn(LOG_REGIO, "couldn't write internal server error: %v", err) + } + return + } + // reset eliona cookies - setCookies = http.Cookie{ + setCookies := http.Cookie{ Name: "elionaAuthorization", Value: "invalid", Path: "/"} - http.SetCookie(w, &setCookies) - http.Redirect(w, r, s.redirectNoLogin, http.StatusFound) - return -internalServerError: - log.Warn(LOG_REGIO, "internal servererror occured while auth") - w.WriteHeader(http.StatusInternalServerError) - _, err = w.Write(errorMessage) - if err != nil { - log.Error(LOG_REGIO, "write internal server error: %v", err) + if s.redirectNoLogin == "" { + // fallback + u, err := url.Parse(s.baseUrl + "/apps-public/saml-sso/error.html") + if err != nil { + log.Error(LOG_REGIO, "cannot parse fallback redirect url: %v", err) + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(errorMsg + ":" + err.Error())) + return + } + queries := url.Values{} + + queries.Add("title", "Login Error") + queries.Add("message", errorMsg) + queries.Add("details", errorMsg) + queries.Add("linkText", "Go To Login") + queries.Add("link", s.baseUrl) + + u.RawQuery = queries.Encode() + + log.Debug(LOG_REGIO, "login failed. redirect to error.html") + http.Redirect(w, r, u.String(), http.StatusFound) + + } else if !s.htmlContent { + // redirect + log.Debug(LOG_REGIO, "login failed. redirect to custom url %s", s.redirectNoLogin) + http.Redirect(w, r, s.redirectNoLogin, http.StatusFound) + } else { + // write html content + log.Debug(LOG_REGIO, "login failed. show custom html content") + _, err := w.Write([]byte(utils.SubstituteError(s.redirectNoLogin, []byte(errorMsg)))) + if err != nil { + log.Error(LOG_REGIO, "write error page content: %v", err) + } } } -func (s *SingleSignOn) setUserPermissions(userId *string) error { +func (s *SingleSignOn) getPermissionsAndLang(samlCtx context.Context) (sysRoleId int, + projRoleId int, lang string, err error) { var ( - err error permissions *apiserver.Permissions + aclRoleMap map[string]int ) + sysRoleId, projRoleId = -1, -1 + lang = DefaultLang + permissions, err = conf.GetPermissionMapping(context.Background()) if err != nil { - return err + return + } + if permissions == nil { + err = errors.New("cannot load permission config. ") + return } - log.Info(LOG_REGIO, - "ToDo: permission map not finished yet. %v", - permissions) - - projectId, err := GetFirstProjectId() + aclRoleMap, err = GetACLRoleMap() if err != nil { - return fmt.Errorf("cannot look up project id: %v", err) + return } - roleId, err := GetRoleIdByDisplayName(permissions.DefaultProjRole) - if err != nil { - return fmt.Errorf("cannot get role id for role %s: %v", - permissions.DefaultProjRole, err) + // get defaults + sysRoleId = conf.StringToRoleId(permissions.DefaultSystemRole, aclRoleMap) + + projRoleId = conf.StringToRoleId(permissions.DefaultProjRole, aclRoleMap) + + lang = permissions.DefaultLanguage + + // if configured, map permissions and lang + if permissions.SystemRoleSamlAttribute != nil && + *permissions.SystemRoleSamlAttribute != "" && + permissions.SystemRoleMap != nil { + + systemRoleMap := conf.ApiRoleMapToGolangMap(*permissions.SystemRoleMap) + + samlValue := samlsp.AttributeFromContext(samlCtx, *permissions.SystemRoleSamlAttribute) + + elionaRoleOrId := systemRoleMap[samlValue] + log.Debug(LOG_REGIO, "saml value for sysRole: %s, elionaRole: %v -> map %v", + samlValue, elionaRoleOrId, systemRoleMap) + sysRoleId = conf.AnyToRoleId(elionaRoleOrId, aclRoleMap) } + if permissions.ProjRoleSamlAttribute != nil && + *permissions.ProjRoleSamlAttribute != "" && + permissions.ProjRoleMap != nil { + + projectRoleMap := conf.ApiRoleMapToGolangMap(*permissions.ProjRoleMap) - if roleId <= 0 { - return fmt.Errorf("cannot get role id for %s", - permissions.DefaultProjRole) + samlValue := samlsp.AttributeFromContext(samlCtx, *permissions.ProjRoleSamlAttribute) + + elionaRoleOrId := projectRoleMap[samlValue] + log.Debug(LOG_REGIO, "saml value for projRole: %s, elionaRole: %v -> map %v", + samlValue, elionaRoleOrId, projectRoleMap) + + projRoleId = conf.AnyToRoleId(elionaRoleOrId, aclRoleMap) + } + if permissions.LanguageSamlAttribute != nil && + *permissions.LanguageSamlAttribute != "" && + permissions.LanguageMap != nil { + + langMap := conf.ApiRoleMapToGolangMap(*permissions.LanguageMap) + + samlValue := samlsp.AttributeFromContext(samlCtx, *permissions.LanguageSamlAttribute) + + elionaLang := langMap[samlValue] + switch l := elionaLang.(type) { + case string: + lang = l + default: + log.Warn(LOG_REGIO, "language after map invalid type: %T, %v", lang, lang) + } + } + + if sysRoleId <= 0 || projRoleId <= 0 || lang == "" { + err = errors.New("mapping unsuccessful") } - return SetProjectUser(projectId, userId, roleId) + return +} + +func (s *SingleSignOn) getProjectId() (projectId string, err error) { + + projectId = os.Getenv("PROJID") + if projectId == "" { + projectId, err = GetFirstProjectId() + } + if err != nil { + err = fmt.Errorf("cannot look up project id: %v", err) + } + + return } diff --git a/go.mod b/go.mod index 7f9a9cb..85f3bc2 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,10 @@ go 1.20 require ( github.com/crewjam/saml v0.4.14 - github.com/eliona-smart-building-assistant/go-eliona v1.9.31 - github.com/eliona-smart-building-assistant/go-eliona-api-client/v2 v2.6.3 - github.com/eliona-smart-building-assistant/go-utils v1.0.61 + github.com/eliona-smart-building-assistant/app-integration-tests v1.1.4 + github.com/eliona-smart-building-assistant/go-eliona v1.9.39 + github.com/eliona-smart-building-assistant/go-eliona-api-client/v2 v2.6.12 + github.com/eliona-smart-building-assistant/go-utils v1.1.1 github.com/friendsofgo/errors v0.9.2 github.com/go-test/deep v1.1.0 github.com/gorilla/mux v1.8.1 @@ -14,8 +15,8 @@ require ( github.com/volatiletech/sqlboiler/v4 v4.16.2 github.com/volatiletech/strmangle v0.0.6 github.com/zenazn/goji v1.0.1 - golang.org/x/crypto v0.20.0 - golang.org/x/net v0.21.0 + golang.org/x/crypto v0.24.0 + golang.org/x/net v0.26.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -24,28 +25,31 @@ require ( replace github.com/ericlagergren/decimal => github.com/ericlagergren/decimal v0.0.0-20181231230500-73749d4874d5 require ( - github.com/beevik/etree v1.3.0 // indirect + github.com/beevik/etree v1.4.0 // indirect github.com/crewjam/httperr v0.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.14.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.2 // indirect - github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect - github.com/jackc/pgtype v1.14.2 // indirect - github.com/jackc/pgx/v4 v4.18.1 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgtype v1.14.3 // indirect + github.com/jackc/pgx/v4 v4.18.3 // indirect github.com/jackc/puddle v1.3.0 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russellhaering/goxmldsig v1.4.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/volatiletech/inflect v0.0.1 // indirect github.com/volatiletech/randomize v0.0.1 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/image v0.15.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect ) diff --git a/go.sum b/go.sum index 274bd6c..a8803f9 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= -github.com/beevik/etree v1.3.0 h1:hQTc+pylzIKDb23yYprodCWWTt+ojFfUZyzU09a/hmU= -github.com/beevik/etree v1.3.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc= +github.com/beevik/etree v1.4.0 h1:oz1UedHRepuY3p4N5OjE0nK1WLCqtzHf25bxplKOHLs= +github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -123,12 +123,14 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eliona-smart-building-assistant/go-eliona v1.9.31 h1:ZmAiODXNOfgtmDKPyvWK4RahDsZ2Z7LvDRV6emLiPYw= -github.com/eliona-smart-building-assistant/go-eliona v1.9.31/go.mod h1:+L/wHUtRPz5U2Km+JCwliSjFmB4FRffGxttlQrC4OFY= -github.com/eliona-smart-building-assistant/go-eliona-api-client/v2 v2.6.3 h1:RpAlMxLJi85CUBMs+ywZYYq1N9EaMtxxr+K5EDSzX7Q= -github.com/eliona-smart-building-assistant/go-eliona-api-client/v2 v2.6.3/go.mod h1:GsmIYeSRwPLX/Kg2yYtxIfhqAvoCjLP/X/k2YALLVSg= -github.com/eliona-smart-building-assistant/go-utils v1.0.61 h1:6LjRpXsovtZ5VBSUKs5ZML//x03efuqWMqycjfVhtyg= -github.com/eliona-smart-building-assistant/go-utils v1.0.61/go.mod h1:rcRDJItD62tXkniILeEWE0LpHHitfDJbJzIDsw71AfE= +github.com/eliona-smart-building-assistant/app-integration-tests v1.1.4 h1:WA8A6Supw+tlwCKac8pEjqHNP2borQ19/fpWzAvGuLE= +github.com/eliona-smart-building-assistant/app-integration-tests v1.1.4/go.mod h1:wxEyTCTtWhIEwcmaKP36immzxdaIKXK/J6VhBmKRj1k= +github.com/eliona-smart-building-assistant/go-eliona v1.9.39 h1:8CD7+pp+yG+0W7m4OsGlMgw9ljTB2vPa4ivvN35NKCc= +github.com/eliona-smart-building-assistant/go-eliona v1.9.39/go.mod h1:b3+Wzp66B8AvystSwjLAFP5Vh0wnsmTG8wSgbVEG8hg= +github.com/eliona-smart-building-assistant/go-eliona-api-client/v2 v2.6.12 h1:DygiZXbvvVEGRV5eTF6MwwcSFyR0X9l4b0WLchGZF0I= +github.com/eliona-smart-building-assistant/go-eliona-api-client/v2 v2.6.12/go.mod h1:+xiOXCnskMRTVXRDi+LCVXjL+1280Aga6HsTPqr5Tz4= +github.com/eliona-smart-building-assistant/go-utils v1.1.1 h1:M2ftQRVRIrFBeHlHaYukblkSb0jBqwG52iwEQNKHXvs= +github.com/eliona-smart-building-assistant/go-utils v1.1.1/go.mod h1:rcRDJItD62tXkniILeEWE0LpHHitfDJbJzIDsw71AfE= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -309,9 +311,8 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= -github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -327,25 +328,26 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= -github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgtype v1.14.2 h1:QBdZQTKpPdBlw2AdKwHEyqUcm/lrl2cwWAHjCMyln/o= -github.com/jackc/pgtype v1.14.2/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.3 h1:h6W9cPuHsRWQFTWUZMAKMgG5jSwQI0Zurzdvlx3Plus= +github.com/jackc/pgtype v1.14.3/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= -github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= @@ -520,8 +522,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -590,9 +592,10 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -605,6 +608,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= +golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -629,6 +634,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -680,8 +686,10 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -715,6 +723,7 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -801,10 +810,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -815,8 +828,10 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -882,6 +897,7 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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/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/html/error.html b/html/error.html new file mode 100644 index 0000000..a99bd14 --- /dev/null +++ b/html/error.html @@ -0,0 +1,90 @@ + + + + + + + +
+ +

SAML 2.0 - Login Error

+


+

Sorry, there was an error during authentication or authorization.

+



+ Back to Login +
+ Details +

501 - Internal Server Error: cannot map any attribute to this User

+
+


+
+ + \ No newline at end of file diff --git a/icon b/icon index a3f3a38..dc432c5 100644 --- a/icon +++ b/icon @@ -1 +1 @@ -data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAmBJREFUeF7tnMEtBWEcB/+rAKIFPSiDuItSiEQr3IULWlADLUgUYMWN80zEyrz7b3bfzMv7bt/yfnKwznY/97u3r8ffX//yae7WmaOtfqWlIH8rXUH+Vo8pSEFUA50hqk4OKwh3qBIKourksIJwhyqhIKpODisId6gSCqLq5LCCcIcqoSCqTg4rCHeoEgqi6uSwgnCHKqEgqk4OKwh3qBIKourksIJwhyqhIKpODisId6gSCqLq5LCCcIcqoSCqTg4rCHeoEgqi6uSwgnCHKqEgqk4OKwh3qBIKourksIJwhyqhIKpODisId6gSCqLq5LCCcIcqoSCqTg4rCHeoEgqi6uSwgnCHKqEgqk4OKwh3qBIKourksIJwhyqhIKpODisId6gS/mWQO1XRL8LWWZ73bl+uvj/y4mnOd2YOf/E11EctKi0YNlAQrNAFFMT1iWkFwQpdwDI3b5s91Odjnuds/8ehvj7M+SxbPtSv37Z7CeYy93O6/+MSzPVxvn5g270Ecwri/udA2lIQaFCeF0QWSnEFoQblfUFkoRRXEGpQ3hdEFkpxBaEG5X1BZKEUVxBqUN4XRBZKcQWhBuV9QWShFFcQalDeF0QWSnEFoQblfUFkoRRXEGpQ3hdEFkpxBaEG5X1BZKEUVxBqUN4XRBZKcQWhBuV9QWShFFcQalDeF0QWSnEFoQblfUFkoRRXEGpQ3hdEFkpxBaEG5X1BZKEUVxBqUN4XRBZKcQWhBuV9QWShFFcQalDeF0QWSnEFoQblfUFkoRRXEGpQ3hdEFkpxBaEG5X1BZKEUVxBqUN5/AhZzzbpqWWGcAAAAAElFTkSuQmCC +data:image/png;base64,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 diff --git a/inegration_test.go b/inegration_test.go index be4fe19..365a772 100644 --- a/inegration_test.go +++ b/inegration_test.go @@ -15,28 +15,27 @@ package main -// import ( -// "testing" +import ( + "testing" -// "github.com/eliona-smart-building-assistant/app-integration-tests/app" -// "github.com/eliona-smart-building-assistant/app-integration-tests/assert" -// "github.com/eliona-smart-building-assistant/app-integration-tests/test" -// ) + "github.com/eliona-smart-building-assistant/app-integration-tests/app" + "github.com/eliona-smart-building-assistant/app-integration-tests/assert" + "github.com/eliona-smart-building-assistant/app-integration-tests/test" +) -// func TestApp(t *testing.T) { -// app.StartApp() -// test.AppWorks(t) -// t.Run("TestSchema", schema) -// app.StopApp() -// } +func TestApp(t *testing.T) { + app.StartApp() + test.AppWorks(t) + t.Run("TestSchema", schema) + app.StopApp() +} -// func schema(t *testing.T) { -// t.Parallel() +func schema(t *testing.T) { + t.Parallel() -// assert.SchemaExists(t, "saml_sp", []string{ -// "basic_config", -// "attribute_map", -// "advanced_config", -// "permissions", -// }) -// } + assert.SchemaExists(t, "saml_sp", []string{ + "config", + "attribute_map", + "permissions", + }) +} diff --git a/openapi.yaml b/openapi.yaml index d90225d..9024fa2 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -23,8 +23,18 @@ externalDocs: description: Find out more about the app saml-sso url: https://github.com/eliona-smart-building-assistant/app-saml-sso servers: - - url: http://saml-sso/v1 - - url: https://cust.eliona.cloud/apps/saml-sso/api/ + - url: https://{customer}.eliona.cloud/apps/saml-sso/api + description: Application API Cloud + variables: + customer: + default: demo + description: Customer Name in a cloud installation + - url: '{server}/apps/saml-sso/api' + description: Application API SaaS and OnPremise + variables: + server: + default: https://eliona.muster.int + description: URL @ Customer security: - ApiKeyAuth: [] @@ -39,60 +49,7 @@ tags: externalDocs: url: https://github.com/eliona-smart-building-assistant/app-saml-sso - - name: SAML2.0 - description: SAML 2.0 specific entpoint - externalDocs: - url: https://github.com/eliona-smart-building-assistant/app-saml-sso - - - name: Generic Single Sign-On - description: Generic endpoints for all Single Sign-On Applications - externalDocs: - url: https://github.com/eliona-smart-building-assistant/sso - paths: - /sso/active: - get: - tags: - - Generic Single Sign-On - summary: Check, if a SSO service is available and configured - description: This endpoint is for checking, if any SSO service is running on Eliona - operationId: getSSOActive - responses: - "200": - description: Successfully returned, if any SSO service is running - content: - application/json: - schema: - $ref: "#/components/schemas/Active" - - /sso/auth: - get: - tags: - - Generic Single Sign-On - summary: Begin authorization / login procedure - description: Startpoint for each SSO service to process the authorization - operationId: getAuthorizationProcedure - responses: - "302": - description: Started login, redirect to the IdP with the SAML request - - /saml/acs: - description: SAML's Assertion Consumer Service - post: - tags: - - SAML2.0 - responses: - "302": - description: SAML response returned from the IdP redirected to the auth endpoint to evaluate and login - - /saml/slo: - description: SAML's Single Logout Service - post: - tags: - - SAML2.0 - responses: - "302": - description: SAML response returned from the IdP redirected to the auth endpoint to logout /configuration: get: @@ -422,40 +379,61 @@ components: readOnly: true default: 1 example: 1 - default_system_role: + defaultSystemRole: type: string readOnly: false nullable: false default: 'System user' example: 'System user' - default_proj_role: + defaultProjRole: type: string nullable: false readOnly: false default: 'Project user' example: 'Project user' - system_role_saml_attribute: + defaultLanguage: + type: string + readOnly: false + nullable: false + example: 'en' + enum: + - en + - de + - it + - fr + systemRoleSamlAttribute: type: string nullable: true readOnly: false example: "systemRightsSamlAttribute" - system_role_map: + systemRoleMap: type: array items: $ref: "#/components/schemas/RoleMap" nullable: true readOnly: false - proj_role_saml_attribute: + projRoleSamlAttribute: type: string nullable: true readOnly: false example: "projectRightsSamlAttribute" - proj_role_map: + projRoleMap: type: array items: $ref: "#/components/schemas/RoleMap" nullable: true readOnly: false + languageSamlAttribute: + type: string + nullable: true + readOnly: false + example: "language" + languageMap: + type: array + nullable: true + readOnly: false + items: + $ref: "#/components/schemas/RoleMap" RoleMap: type: object @@ -470,17 +448,6 @@ components: nullable: false example: "Administrator" - Active: - type: object - description: If the service is active - properties: - active: - type: boolean - nullable: false - readOnly: true - default: true - example: true - securitySchemes: ApiKeyAuth: description: Use the API key as a secret for authorizing and identifying an app or agent diff --git a/openapi_saml_public.yaml b/openapi_saml_public.yaml new file mode 100644 index 0000000..2a48a6f --- /dev/null +++ b/openapi_saml_public.yaml @@ -0,0 +1,118 @@ +openapi: 3.0.3 + +# This file is part of the eliona project. +# Copyright © 2023 Eliona by IoTEC AG. All Rights Reserved. +# ______ _ _ +# | ____| (_) +# | |__ | |_ ___ _ __ __ _ +# | __| | | |/ _ \| '_ \ / _` | +# | |____| | | (_) | | | | (_| | +# |______|_|_|\___/|_| |_|\__,_| +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +info: + version: 1.0.0 + title: App SAML 2.0 SSO SAML specific Public Endpoints + description: Endpoints related to the SAML 2.0 Workflow +externalDocs: + description: Find out more about the app saml-sso + url: https://github.com/eliona-smart-building-assistant/app-saml-sso +servers: + - url: https://{customer}.eliona.cloud/apps-public/saml-sso + description: SAML Endpoints on Cloud + variables: + customer: + default: demo + description: Customer Name in a cloud installation + - url: '{server}/apps-public/saml-sso' + description: SAML Endpoints SaaS and OnPremise + variables: + server: + default: https://eliona.muster.int + description: URL @ Customer + +tags: + - name: 'SAML2.0' + description: SAML 2.0 specific entpoint + externalDocs: + url: https://github.com/eliona-smart-building-assistant/app-saml-sso + + - name: 'Generic Single Sign-On' + description: Generic endpoints for all Single Sign-On Applications + externalDocs: + url: https://github.com/eliona-smart-building-assistant/sso + +paths: + /active: + get: + tags: + - 'Generic Single Sign-On' + summary: Check, if a SSO service is available and configured + description: This endpoint is for checking, if any SSO service is running on Eliona + operationId: getSSOActive + responses: + "200": + description: Successfully returned, if any SSO service is running + content: + application/json: + schema: + $ref: "#/components/schemas/Active" + + /auth: + get: + tags: + - 'Generic Single Sign-On' + summary: Begin authorization / login procedure + description: Startpoint for each SSO service to process the authorization + operationId: getAuthorizationProcedure + responses: + "302": + description: Started login, redirect to the IdP with the SAML request + + /saml/acs: + description: SAML's Assertion Consumer Service + post: + tags: + - 'SAML2.0' + responses: + "302": + description: SAML response returned from the IdP redirected to the auth endpoint to evaluate and login + + /saml/slo: + description: SAML's Single Logout Service + post: + tags: + - 'SAML2.0' + responses: + "302": + description: SAML response returned from the IdP redirected to the auth endpoint to logout + + /saml/metadata: + description: Metadata of this SAML Service Provider (SP) + get: + tags: + - 'SAML2.0' + responses: + "200": + description: Successfully returned, if any SSO service is running + content: + application/xml: + example: ... + +components: + schemas: + Active: + type: object + description: If the service is active + properties: + active: + type: boolean + nullable: false + readOnly: true + default: true + example: true diff --git a/saml/service_provider.go b/saml/service_provider.go index a0ea910..a205cad 100644 --- a/saml/service_provider.go +++ b/saml/service_provider.go @@ -17,6 +17,7 @@ package saml import ( "crypto/rsa" + "net/http" "net/url" "saml-sso/utils" @@ -25,26 +26,31 @@ import ( ) const ( - LOG_REGIO = "service provider" + LOG_REGIO = "service provider" + SP_HANDLE_BASE_PATH = "/saml/" // used from saml middleware for ACS, ACL, etc. + PUBLIC_BASE_PATH = "/apps-public/saml-sso" ) type ServiceProvider struct { - sp *samlsp.Middleware + pubBasePath string + sp *samlsp.Middleware } -func NewServiceProvider(certificate string, privateKey string, baseUrl string, +func NewServiceProvider(certificate string, privateKey string, pubBaseUrl string, idpMetadata []byte) (*ServiceProvider, error) { - return NewServiceProviderAdvanced(certificate, privateKey, baseUrl, idpMetadata, nil, nil, - nil, nil, nil) + return NewServiceProviderAdvanced(certificate, privateKey, pubBaseUrl, idpMetadata, nil, nil, + nil, nil, nil, "") } func NewServiceProviderAdvanced(certificate string, privateKey string, baseUrl string, idpMetadata []byte, entityId *string, allowInitByIdp *bool, signedRequest *bool, forceAuthn *bool, - cookieSecure *bool) (*ServiceProvider, error) { - var serviceProvider ServiceProvider = ServiceProvider{} + cookieSecure *bool, pubBasePath string) (*ServiceProvider, error) { + var serviceProvider ServiceProvider = ServiceProvider{ + pubBasePath: pubBasePath, + } - rootUrl, err := url.Parse(baseUrl) + rootUrl, err := url.Parse(baseUrl + pubBasePath + "/") if err != nil { return nil, err } @@ -62,14 +68,15 @@ func NewServiceProviderAdvanced(certificate string, privateKey string, baseUrl s } opts := samlsp.Options{ - URL: *rootUrl, - Key: keyPair.PrivateKey.(*rsa.PrivateKey), - Certificate: keyPair.Leaf, - IDPMetadata: idpMeta, + URL: *rootUrl, + Key: keyPair.PrivateKey.(*rsa.PrivateKey), + Certificate: keyPair.Leaf, + IDPMetadata: idpMeta, + DefaultRedirectURI: PUBLIC_BASE_PATH + "/", } if entityId != nil { - opts.EntityID = utils.SubstituteOwnUrlUrlString(*entityId, baseUrl) + opts.EntityID, _ = utils.SubstituteOwnUrlUrlString(*entityId, baseUrl) } if allowInitByIdp != nil { opts.AllowIDPInitiated = *allowInitByIdp @@ -82,14 +89,29 @@ func NewServiceProviderAdvanced(certificate string, privateKey string, baseUrl s } if cookieSecure != nil { // opts.CookieSecure: true // option not available any more - log.Debug(LOG_REGIO, "not implemented") + log.Debug(LOG_REGIO, "cookie secure not implemented") } serviceProvider.sp, err = samlsp.New(opts) + log.Debug(LOG_REGIO, "ACS URL %v", serviceProvider.sp.ServiceProvider.AcsURL) + log.Info(LOG_REGIO, "Metadata URL %v", serviceProvider.sp.ServiceProvider.MetadataURL) + log.Debug(LOG_REGIO, "SLO URL %v", serviceProvider.sp.ServiceProvider.SloURL) + return &serviceProvider, err } func (s *ServiceProvider) GetMiddleWare() *samlsp.Middleware { + return s.sp } + +func (s *ServiceProvider) FixPath(next http.Handler) http.Handler { + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + r.URL.Path = s.pubBasePath + r.URL.Path + + next.ServeHTTP(w, r) + }) +} diff --git a/sqlboiler.toml b/sqlboiler.toml index 44fe5c5..5414d14 100644 --- a/sqlboiler.toml +++ b/sqlboiler.toml @@ -6,11 +6,11 @@ no-tests = true add-enum-types = true [psql] -dbname = "postgres" +dbname = "iot" host = "localhost" -port = 5432 -user = "postgres" -pass = "secret" +port = 5433 +user = "leicom" +pass = ".test" schema = "saml_sp" sslmode = "disable" diff --git a/utils/config.go b/utils/config.go index 24cceb5..3af792f 100644 --- a/utils/config.go +++ b/utils/config.go @@ -19,8 +19,19 @@ import "strings" const ( UTILS_OWN_URL_PLACEHOLDER = "{ownUrl}" + UTILS_ERROR_PLACEHODER = "{error}" ) -func SubstituteOwnUrlUrlString(url string, ownUrl string) string { - return strings.ReplaceAll(url, UTILS_OWN_URL_PLACEHOLDER, ownUrl) +func SubstituteOwnUrlUrlString(url string, ownUrl string) (urlOrHtml string, isHtml bool) { + isHtml = false + + if strings.Contains(url, "") { + isHtml = true + } + + return strings.ReplaceAll(url, UTILS_OWN_URL_PLACEHOLDER, ownUrl), isHtml +} + +func SubstituteError(content string, err []byte) (result string) { + return strings.ReplaceAll(content, UTILS_ERROR_PLACEHODER, string(err)) } diff --git a/utils/config_test.go b/utils/config_test.go index f1363f3..33205f6 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -28,8 +28,8 @@ func TestApp_Utils_ConfigSubstitution(t *testing.T) { expected string = "https://example.org/metadata" ) - is := utils.SubstituteOwnUrlUrlString(test, ownUrl) - if is != expected { + is, isHtml := utils.SubstituteOwnUrlUrlString(test, ownUrl) + if is != expected || isHtml { t.Error("subistitution of {ownUrl}") } } diff --git a/utils/testing.go b/utils/testing.go index 3ae1133..ad4a8c5 100644 --- a/utils/testing.go +++ b/utils/testing.go @@ -78,6 +78,9 @@ func CreateRandomApiPermissions() apiserver.Permissions { SystemRoleMap: nil, // ToDo ProjRoleSamlAttribute: nil, // ToDo ProjRoleMap: nil, // ToDo + DefaultLanguage: "en", + LanguageSamlAttribute: nil, // ToDo + LanguageMap: nil, // ToDo } if RandomBoolean() { diff --git a/utils/x509.go b/utils/x509.go index 33c4ebc..ac0894d 100644 --- a/utils/x509.go +++ b/utils/x509.go @@ -39,8 +39,14 @@ const ( CERT_COMMON_NAME_CN = "eliona-saml-sp" ) -func GetCombinedX509Certificate(certificate string, privateKey string) (tls.Certificate, error) { - return tls.X509KeyPair([]byte(certificate), []byte(privateKey)) +func GetCombinedX509Certificate(certificate string, privateKey string) (pair tls.Certificate, err error) { + pair, err = tls.X509KeyPair([]byte(certificate), []byte(privateKey)) + if err != nil { + return + } + + pair.Leaf, err = x509.ParseCertificate(pair.Certificate[0]) + return } func CreateSelfsignedX509Certificate(validityDays int,