diff --git a/README.md b/README.md index d7f0bb7..c5e5e6b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Argo Api Authn -Jenkins [![Build Status](https://jenkins.argo.grnet.gr/job/argo-api-authn_devel/badge/icon)](https://jenkins.argo.grnet.gr/job/argo-api-authn_devel) +[![Build Status](https://jenkins.einfra.grnet.gr/buildStatus/icon?job=ARGO%2Fargo-api-authn%2Fmaster&style=flat-square&color=darkturquoise&subject=build-master)](https://jenkins.einfra.grnet.gr/job/ARGO/job/argo-api-authn/job/master/) +[![Build Status](https://jenkins.einfra.grnet.gr/buildStatus/icon?job=ARGO%2Fargo-api-authn%2Fdevel&style=flat-square&subject=build-devel)](https://jenkins.einfra.grnet.gr/job/ARGO/job/argo-api-authn/job/devel/) Authentication Service for ARGO API(s) @@ -75,7 +76,7 @@ Before you start, you need to issue a valid certificate. "trust_unknown_cas": false, "verify_certificate": true, "service_types_paths": { - "ams": "/v1/users:byUUID/{{identifier}}?key={{access_key}}", + "ams": "/v1/users:byUUID/{{identifier}}", "web-api": "/api/v2/users:byID/{{identifier}}?export=flat" }, "service_types_retrieval_fields": { @@ -127,4 +128,4 @@ but a reverse dns look up returns another hostname for the client from where the - ~~Add default configuration for interacting easier with the [argo-web-api](https://github.com/ARGOeu/argo-web-api).~~ -- Add support for using OIDC tokens as an alternative authentication mechanism. \ No newline at end of file +- Add support for using OIDC tokens as an alternative authentication mechanism. diff --git a/argo-api-authn.spec b/argo-api-authn.spec index 07755ce..868141d 100644 --- a/argo-api-authn.spec +++ b/argo-api-authn.spec @@ -3,7 +3,7 @@ Name: argo-api-authn Summary: ARGO Authentication API. Map X509, OICD to token. -Version: 0.1.8 +Version: 1.0.0 Release: 1%{?dist} License: ASL 2.0 Buildroot: %{_tmppath}/%{name}-buildroot @@ -57,6 +57,8 @@ go clean %attr(0644,root,root) /usr/lib/systemd/system/argo-api-authn.service %changelog +* Mon Oct 10 2022 Agelos Tsalapatis - 1.0.0-1%{?dist} +- Release of argo-api-authn version 1.0.0 * Mon Nov 8 2021 Agelos Tsalapatis - 0.1.8-1%{?dist} - Release of argo-api-authn version 0.1.8 * Tue Apr 13 2021 Agelos Tsalapatis - 0.1.7-1%{?dist} diff --git a/auth/certificate.go b/auth/certificate.go index 96cd4b7..6e05f5b 100644 --- a/auth/certificate.go +++ b/auth/certificate.go @@ -64,7 +64,7 @@ func LoadCAs(dir string) (roots *x509.CertPool) { ).Error("Error walking certificate system path") } else { log.WithFields( - log.Fields{}, + log.Fields{"type": "service_log"}, ).Info("All certificates parsed successfully!") } diff --git a/authmethods/api_key_auth.go b/authmethods/api_key_auth.go index 185f99e..0cf3a16 100644 --- a/authmethods/api_key_auth.go +++ b/authmethods/api_key_auth.go @@ -57,7 +57,7 @@ func (m *ApiKeyAuthMethod) Update(r io.ReadCloser) (AuthMethod, error) { var authMBytes []byte var tempAM TempApiKeyAuthMethod - var updatedAM = NewApiKeyAuthMethod() + var updatedAM = &ApiKeyAuthMethod{} // first fill the temp auth method with the already existing data // convert the existing auth method to bytes @@ -104,6 +104,7 @@ func (m *ApiKeyAuthMethod) Update(r io.ReadCloser) (AuthMethod, error) { return updatedAM, err } + updatedAM.UpdatedOn = utils.ZuluTimeNow() return updatedAM, err } diff --git a/authmethods/api_key_auth_test.go b/authmethods/api_key_auth_test.go index 0500381..7e3f825 100644 --- a/authmethods/api_key_auth_test.go +++ b/authmethods/api_key_auth_test.go @@ -66,7 +66,7 @@ func (suite *ApiKeyAuthMethodTestSuite) TestApiKeyAuthFinder() { func (suite *ApiKeyAuthMethodTestSuite) TestUpdate() { - apk1 := ApiKeyAuthMethod{} + apk1 := &ApiKeyAuthMethod{} ba1 := BasicAuthMethod{ServiceUUID: "uuid1", Host: "host1", Port: 9000, Type: "api-key"} apk1.BasicAuthMethod = ba1 @@ -76,6 +76,8 @@ func (suite *ApiKeyAuthMethodTestSuite) TestUpdate() { apkUpd1.BasicAuthMethod = baUpd1 r1 := ConvertAuthMethodToReadCloser(apkUpd1) a1, err1 := apk1.Update(r1) + ca1 := a1.(*ApiKeyAuthMethod) + apkUpd1.UpdatedOn = ca1.UpdatedOn // update fields that aren't supposed to be updated apkUpd2 := &ApiKeyAuthMethod{} @@ -83,9 +85,11 @@ func (suite *ApiKeyAuthMethodTestSuite) TestUpdate() { apkUpd2.BasicAuthMethod = baUpd2 r2 := ConvertAuthMethodToReadCloser(apkUpd2) a2, err2 := apk1.Update(r2) + ca2 := a2.(*ApiKeyAuthMethod) + apk1.UpdatedOn = ca2.UpdatedOn suite.Equal(apkUpd1, a1) - suite.NotEqual(apk1, a2) + suite.Equal(apk1, a2) suite.Nil(err1) suite.Nil(err2) diff --git a/authmethods/authmethods_test.go b/authmethods/authmethods_test.go index 277cc3b..4476ffb 100644 --- a/authmethods/authmethods_test.go +++ b/authmethods/authmethods_test.go @@ -195,6 +195,8 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodUpdate() { amU1.BasicAuthMethod = ambU1 r1 := ConvertAuthMethodToReadCloser(amU1) a1, err1 := AuthMethodUpdate(am1, r1, mockstore) + ca1 := a1.(*ApiKeyAuthMethod) + amU1.UpdatedOn = ca1.UpdatedOn // normal case - update fields that can't be updated ambU2 := BasicAuthMethod{ServiceUUID: "uuid1", Host: "host1", Port: 9000, Type: "some_api-key", UUID: "some_am_uuid_1", CreatedOn: "some_time"} @@ -202,6 +204,8 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodUpdate() { amU2.BasicAuthMethod = ambU2 r2 := ConvertAuthMethodToReadCloser(amU2) a2, err2 := AuthMethodUpdate(am1, r2, mockstore) + amU2.UpdatedOn = ca1.UpdatedOn + am1.UpdatedOn = ca1.UpdatedOn // unknown service uuid ambU3 := BasicAuthMethod{ServiceUUID: "unknown", Host: "host1", Port: 9000, Type: "api-key", UUID: "am_uuid_1", CreatedOn: ""} @@ -209,6 +213,7 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodUpdate() { amU3.BasicAuthMethod = ambU3 r3 := ConvertAuthMethodToReadCloser(amU3) a3, err3 := AuthMethodUpdate(am1, r3, mockstore) + amU3.UpdatedOn = ca1.UpdatedOn // unknown host ambU4 := BasicAuthMethod{ServiceUUID: "uuid1", Host: "unknown", Port: 9000, Type: "api-key", UUID: "am_uuid_1", CreatedOn: ""} @@ -216,6 +221,7 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodUpdate() { amU4.BasicAuthMethod = ambU4 r4 := ConvertAuthMethodToReadCloser(amU4) a4, err4 := AuthMethodUpdate(am1, r4, mockstore) + amU4.UpdatedOn = ca1.UpdatedOn // empty service uuid ambU6 := BasicAuthMethod{ServiceUUID: "", Host: "host1", Port: 9000, Type: "api-key", UUID: "am_uuid_1", CreatedOn: ""} @@ -223,6 +229,7 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodUpdate() { amU6.BasicAuthMethod = ambU6 r6 := ConvertAuthMethodToReadCloser(amU6) a6, err6 := AuthMethodUpdate(am1, r6, mockstore) + amU6.UpdatedOn = ca1.UpdatedOn // empty host ambU7 := BasicAuthMethod{ServiceUUID: "uuid1", Host: "", Port: 9000, Type: "api-key", UUID: "am_uuid_1", CreatedOn: ""} @@ -230,6 +237,7 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodUpdate() { amU7.BasicAuthMethod = ambU7 r7 := ConvertAuthMethodToReadCloser(amU7) a7, err7 := AuthMethodUpdate(am1, r7, mockstore) + amU7.UpdatedOn = ca1.UpdatedOn // empty port ambU8 := BasicAuthMethod{ServiceUUID: "uuid1", Host: "host1", Port: 0, Type: "api-key", UUID: "am_uuid_1", CreatedOn: ""} @@ -237,6 +245,7 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodUpdate() { amU8.BasicAuthMethod = ambU8 r8 := ConvertAuthMethodToReadCloser(amU8) a8, err8 := AuthMethodUpdate(am1, r8, mockstore) + amU8.UpdatedOn = ca1.UpdatedOn // empty access key ambU10 := BasicAuthMethod{ServiceUUID: "uuid1", Host: "host1", Port: 10000, Type: "api-key", UUID: "am_uuid_1", CreatedOn: ""} @@ -244,6 +253,7 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodUpdate() { amU10.BasicAuthMethod = ambU10 r10 := ConvertAuthMethodToReadCloser(amU10) a10, err10 := AuthMethodUpdate(am1, r10, mockstore) + amU10.UpdatedOn = ca1.UpdatedOn // auth method for host and service already exists amb2 := BasicAuthMethod{ServiceUUID: "uuid1", Host: "host1", Port: 9000, Type: "api-key", UUID: "am_uuid_1", CreatedOn: ""} @@ -254,6 +264,7 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodUpdate() { amU11.BasicAuthMethod = ambU11 r11 := ConvertAuthMethodToReadCloser(amU11) a11, err11 := AuthMethodUpdate(am2, r11, mockstore) + amU11.UpdatedOn = ca1.UpdatedOn suite.Equal(a1, amU1) suite.Equal(a2, am1) diff --git a/authmethods/basic_auth_method.go b/authmethods/basic_auth_method.go index cc564a6..54221c9 100644 --- a/authmethods/basic_auth_method.go +++ b/authmethods/basic_auth_method.go @@ -13,6 +13,7 @@ type BasicAuthMethod struct { Type string `json:"type" required:"true"` UUID string `json:"uuid"` CreatedOn string `json:"created_on"` + UpdatedOn string `json:"updated_on,omitempty"` } // TempBasicAuthMethod represents the fields that are allowed to be modified diff --git a/authmethods/headers_auth.go b/authmethods/headers_auth.go index b924e8f..f7925ce 100644 --- a/authmethods/headers_auth.go +++ b/authmethods/headers_auth.go @@ -63,7 +63,7 @@ func (m *HeadersAuthMethod) Update(r io.ReadCloser) (AuthMethod, error) { var authMBytes []byte var tempAM TempHeadersAuthMethod - var updatedAM = NewHeadersAuthMethod() + var updatedAM = &HeadersAuthMethod{} // first fill the temp auth method with the already existing data // convert the existing auth method to bytes @@ -110,6 +110,7 @@ func (m *HeadersAuthMethod) Update(r io.ReadCloser) (AuthMethod, error) { return updatedAM, err } + m.UpdatedOn = utils.ZuluTimeNow() return updatedAM, err } diff --git a/authmethods/headers_auth_test.go b/authmethods/headers_auth_test.go index edb7a86..9580a6d 100644 --- a/authmethods/headers_auth_test.go +++ b/authmethods/headers_auth_test.go @@ -69,6 +69,8 @@ func (suite *HeadersAuthMethodTestSuite) TestUpdate() { hamUpd1 := HeadersAuthMethod{BasicAuthMethod: amb2, Headers: map[string]string{"x-api-key": "key-2", "Accept": "application/json"}} r1 := ConvertAuthMethodToReadCloser(&hamUpd1) a1, err1 := hamUpd1.Update(r1) + ca1 := a1.(*HeadersAuthMethod) + ham.UpdatedOn = ca1.UpdatedOn // update fields that aren't supposed to be updated apkUpd2 := &HeadersAuthMethod{} diff --git a/bin/argo-api-authn-scripts/ams-create-users-cloud-info.py b/bin/argo-api-authn-scripts/ams-create-users-cloud-info.py index 6db23a6..053710f 100755 --- a/bin/argo-api-authn-scripts/ams-create-users-cloud-info.py +++ b/bin/argo-api-authn-scripts/ams-create-users-cloud-info.py @@ -12,6 +12,7 @@ import ldap import re import urllib.parse +from argo_ams_library import ArgoMessagingService, AmsUser, AmsUserProject, AmsException, AmsServiceException # set up logging LOGGER = logging.getLogger("AMS User create script per site") @@ -97,7 +98,7 @@ def _assign_rdn_to_field(self, rdn_type, rdn_value): @staticmethod def _escape_rdn_string(dn_string): """ - Method that checks and escapes any possible single slash characters in RDN values + Method that checks and escapes any possible single slash characters and commas in RDN values :param dn_string: :return: the escaped string @@ -107,8 +108,7 @@ def _escape_rdn_string(dn_string): tokens = list(filter(None,re_match_key.split(dn_string))) escaped_string = "".join(x.replace("/","\/") if not re_match_key.match(x) else x for x in tokens) - return escaped_string - + return escaped_string.replace(",", "\,") def _parse_dn_string_ldap_util(self, dn_string): """ @@ -301,6 +301,8 @@ def __str__(self): self._format_rdn_to_string("DC", self.DomainComponent)) return "".join(x for x in printable_string) + + def create_users(config, verify): # retrieve ams info @@ -325,6 +327,9 @@ def create_users(config, verify): # cert key tuple cert_creds = (config.get("AMS", "cert"), config.get("AMS", "cert_key")) + # init the Argo Messaging Service + ams = ArgoMessagingService(endpoint=ams_host, token=ams_token, project=ams_project) + conf_services = config.get("AMS", "service-types").split(",") for srv_type in conf_services: @@ -342,8 +347,8 @@ def create_users(config, verify): # form the goc db url goc_db_url = goc_db_url_arch.replace("{{service-type}}", srv_type) - LOGGER.info("\nAccessing url: " + goc_db_url) - LOGGER.info("\nStarted the process for service-type: " + srv_type) + LOGGER.info("Accessing url: " + goc_db_url) + LOGGER.info("Started the process for service-type: " + srv_type) # grab the xml data from goc db goc_request = requests.get(url=goc_db_url, cert=cert_creds, verify=False) @@ -352,6 +357,8 @@ def create_users(config, verify): # users from goc db that don't have a dn registered missing_dns = [] + not_in_production_endpoints = [] + # build the xml object root = ET.fromstring(goc_request.text) # iterate through the xml object's service_endpoints @@ -368,6 +375,12 @@ def create_users(config, verify): hostname = service_endpoint.find("HOSTNAME").text.replace(".", "-") sitename = service_endpoint.find("SITENAME").text.replace(".", "-") + # check if the endpoint is in production + if service_endpoint.find("IN_PRODUCTION").text != "Y": + LOGGER.info("Skipping not in production endpoint: " + hostname) + not_in_production_endpoints.append(hostname) + continue + # try to get the site's contact email contact_email = ams_email # check the if we have retrieved this site's contact email before @@ -395,8 +408,8 @@ def create_users(config, verify): site_contact_emails[site_name] = contact_email except Exception as e: - LOGGER.warning("Skipping endpoint {} under site {}, {}".format( - hostname, site_name, e)) + LOGGER.warning("Skipping endpoint {0} under site {1}, {2}".format( + hostname, site_name, str(e))) # Create AMS user user_binding_name = \ @@ -407,13 +420,14 @@ def create_users(config, verify): service_dn = RdnSequence(service_dn.text).__str__() except ValueError as ve: LOGGER.error( - "Invalid DN: {}. Exception: {}". - format(service_dn.text, ve)) + "Invalid DN: {0}. Exception: {1}". + format(service_dn.text, str(ve))) continue # check if the given DN already corresponds to a binding # if the DN is already in use, skip the creation process and only perform the steps where the user - # is being assigned to the topic's and sub's acl and the respective topic and subscription are being created. + # is being assigned to the topic's and sub's acl + # and the respective topic and subscription are being created. # TODO replace ams(service type name) with config value binding_exists_url = "https://{0}/v1/service-types/ams/hosts/{1}/bindings?key={2}&authID={3}".format( @@ -431,45 +445,46 @@ def create_users(config, verify): # else if the Dn isn't in use, go through the full process of creating or updating an existing binding elif binding_exists_req.status_code == 404: - project = {'project': ams_project, 'roles': [users_role]} - usr_create = {'projects': [project], 'email': contact_email} - - # create the user - ams_user_crt_url = 'https://{0}/v1/projects/{1}/members/{2}?key={3}'.format( - ams_host, ams_project, user_binding_name, ams_token) - ams_usr_crt_req = requests.post(url=ams_user_crt_url, data=json.dumps(usr_create), verify=verify) - LOGGER.info(ams_usr_crt_req.text) - ams_user_uuid = "" - # if the response is neither a 200(OK) nor a 409(already exists) - # then move on to the next user - if ams_usr_crt_req.status_code != 200 and ams_usr_crt_req.status_code != 409: - LOGGER.critical("\nUser: " + user_binding_name) - LOGGER.critical( - "\nSomething went wrong while creating ams user." + - "\nBody data: " + str(usr_create) + "\nResponse Body: " + - ams_usr_crt_req.text) - continue - - if ams_usr_crt_req.status_code == 200: - ams_user_uuid = ams_usr_crt_req.json()["uuid"] - # count how many users have been created + user_project = AmsUserProject( + project=ams_project, + roles=[users_role] + ) + user_create_data = AmsUser( + projects=[user_project], + email=contact_email, + name=user_binding_name + ) + + exists = False + try: + # create the user + created_ams_user = ams.create_user(user=user_create_data, verify=verify) + LOGGER.info("Created user: " + created_ams_user.name) + ams_user_uuid = created_ams_user.uuid user_count += 1 + except AmsException as e: + if isinstance(e, AmsServiceException) and e.code == 409: + exists = True + else: + LOGGER.error("User: " + user_binding_name) + LOGGER.error( + "Something went wrong while creating ams user." + + "\nError: " + str(e)) + continue # If the user already exists, Get user by username - if ams_usr_crt_req.status_code == 409: - proj_member_list_url = "https://{0}/v1/projects/{1}/members/{2}?key={3}".format(ams_host, ams_project, user_binding_name, ams_token) - ams_usr_get_req = requests.get(url=proj_member_list_url, verify=verify) - - # if the user retrieval was ok - if ams_usr_get_req.status_code == 200: - LOGGER.info("\nSuccessfully retrieved user {} from ams".format(user_binding_name)) - ams_user_uuid = ams_usr_get_req.json()["uuid"] - else: - LOGGER.critical( - "\nCould not retrieve user {} from ams." - "\n Response {}".format(user_binding_name, ams_usr_get_req.text)) + if exists: + try: + ams_user_uuid = \ + ams.get_project_member(username=user_binding_name, + verify=verify).uuid + LOGGER.info("Successfully retrieved user: " + user_binding_name) + except AmsException as ae: + LOGGER.error( + "Could not retrieve user {0} from ams." + "\nError: {1}".format(user_binding_name, str(ae))) continue # Create the respective AUTH binding @@ -521,116 +536,101 @@ def create_users(config, verify): continue # add the user to the AMS project with corresponding role - add_user_project_url = "https://{0}/v1/projects/{1}/members/{2}:add?key={3}".format(ams_host, - ams_project, - user_binding_name, - ams_token) - - add_user_project_req_body = { - "project": ams_project, - "roles": [users_role] - } - - LOGGER.info("Adding user {0} to project {1} . . .".format(user_binding_name, ams_project)) - - add_user_project_req = requests.post(url=add_user_project_url, - data=json.dumps(add_user_project_req_body), verify=verify) - - if add_user_project_req.status_code != 200 and add_user_project_req.status_code != 409: - LOGGER.info("Could not add user {0} to project {1}.\nResponse {2}".format(user_binding_name, - ams_project, - add_user_project_req.text)) - continue + try: + ams.add_project_member(username=user_binding_name, + roles=[users_role], + verify=verify) + except AmsException as e: + if isinstance(e, AmsServiceException) and e.code == 409: + pass + else: + LOGGER.error("Could not add user {0} to project {1}.\nError: {2}".format(user_binding_name, + ams_project, + str(e))) + continue - # since both the ams user was created or already existed AND the authn binding was created or already existed + # since both the ams user was created or already existed + # AND the authn binding was created or already existed # move to topic and subscription creation # create new topic primary_key = service_endpoint. \ find("PRIMARY_KEY").text.replace(' ', '') topic_name = 'SITE_' + sitename + '_ENDPOINT_' + primary_key - topic_crt_req = requests.put( - "https://" + ams_host + "/v1/projects/" + ams_project + - "/topics/" + topic_name + "?key=" + ams_token, verify=verify) - topic_authorized_users = [user_binding_name] - if topic_crt_req.status_code != 200: - if topic_crt_req.status_code != 409: - LOGGER.critical( - "Something went wrong while creating topic " + - topic_name + "\nResponse: " + topic_crt_req.text) - continue + topic_exists = False + + try: + ams.create_topic(topic=topic_name, verify=verify) + except AmsException as e: + if isinstance(e, AmsServiceException) and e.code == 409: + topic_exists = True else: - get_topic_acl_req = requests.get( - "https://" + ams_host + "/v1/projects/" + ams_project + - "/topics/" + topic_name + ":acl?key=" + ams_token, - verify=verify) - if get_topic_acl_req.status_code == 200: - acl_users = json.loads(get_topic_acl_req.text) - topic_authorized_users = topic_authorized_users + acl_users['authorized_users'] - # remove duplicates - topic_authorized_users = list(set(topic_authorized_users)) - - # modify the authorized users - modify_topic_req = requests.post( - "https://" + ams_host + "/v1/projects/" + ams_project + - "/topics/" + topic_name + ":modifyAcl?key=" + ams_token, - data=json.dumps({'authorized_users': topic_authorized_users}), - verify=verify) - LOGGER.critical( - "Modified ACL for topic: {0} with users {1}." - "Response from AMS {2}". - format( - topic_name, str(user_binding_name), modify_topic_req.text)) + LOGGER.error("Could not create topic: {0}.\nError: {1}".format(topic_name, str(e))) + continue + + # modify the topic's acl + # check the already existing acl for the topic + if topic_exists: + try: + acl = ams.getacl_topic(topic=topic_name, verify=verify) + # remove duplicates + topic_authorized_users = list(set(topic_authorized_users + acl["authorized_users"])) + except AmsException as ae: + LOGGER.error("Couldn't get ACL for topic {0}.\nError: {1}".format(topic_name, str(ae))) + continue + + try: + ams.modifyacl_topic(topic=topic_name, users=topic_authorized_users, verify=verify) + LOGGER.info( + "Modified ACL for topic: {0} with users {1}.".format(topic_name, str(topic_authorized_users))) + except AmsException as ae: + LOGGER.error("Could not modify ACL for topic {0}.\nError: {1}".format(topic_name, str(ae))) + continue # create new sub primary_key = service_endpoint.find("PRIMARY_KEY").text.replace(' ', '') sub_name = 'SITE_' + sitename + '_ENDPOINT_' + primary_key sub_authorized_users = [ams_consumer] - sub_data = dict() - sub_data["topic"] = "projects/" + ams_project + "/topics/" + sub_name - sub_data["ackDeadlineSeconds"] = 100 - - sub_crt_req = requests.put( - "https://" + ams_host + "/v1/projects/" + ams_project + - "/subscriptions/" + sub_name + "?key=" + ams_token, data=json.dumps(sub_data),verify=verify) - - if sub_crt_req.status_code != 200 and sub_crt_req.status_code != 409: - LOGGER.critical( - "Something went wrong while creating subscription " + - sub_name + "\nResponse: " + sub_crt_req.text) - - if sub_crt_req.status_code == 409: - get_sub_acl_req = requests.get( - "https://" + ams_host + "/v1/projects/" + ams_project + - "/subscriptions/" + sub_name + ":acl?key=" + ams_token, - verify=verify) - if get_sub_acl_req.status_code == 200: - acl_users = json.loads(get_sub_acl_req.text) - sub_authorized_users = sub_authorized_users + acl_users['authorized_users'] - # remove duplicates - sub_authorized_users = list(set(sub_authorized_users)) - - # modify the authorized users - modify_sub_req = requests.post( - "https://" + ams_host + "/v1/projects/" + ams_project + - "/subscriptions/" + sub_name + ":modifyAcl?key=" + ams_token, - data=json.dumps({'authorized_users': sub_authorized_users}), - verify=verify) - LOGGER.critical( - "Modified ACL for subscription: {0} with users {1}." - "Response from AMS {2}". - format( - sub_name, sub_authorized_users, modify_sub_req.text)) - - LOGGER.critical("Service Type: " + srv_type) - LOGGER.critical("Missing DNS: " + str(missing_dns)) - LOGGER.critical("Total Users Created: " + str(user_count)) - LOGGER.critical("Total Bindings Updated: " + str(update_binding_count)) - LOGGER.critical("Updated bingings: " + str(update_bindings_names)) + sub_exists = False + + try: + ams.create_sub(sub=sub_name, + topic=topic_name, + ackdeadline=100, + verify=verify) + except AmsException as e: + if isinstance(e, AmsServiceException) and e.code == 409: + sub_exists = True + else: + LOGGER.error("Could not create sub: {0}.\nError: {1}".format(sub_name, str(e))) + continue + # modify the sub's acl + # check the already existing acl for the subscription + if sub_exists: + try: + acl = ams.getacl_sub(sub=sub_name, verify=verify) + # remove duplicates + sub_authorized_users = list(set(sub_authorized_users + acl["authorized_users"])) + except AmsException as ae: + LOGGER.error("Couldn't get ACL for sub {0}.\nError: {1}".format(sub_name, str(ae))) + continue + + try: + ams.modifyacl_sub(sub=sub_name, users=sub_authorized_users, verify=verify) + LOGGER.info( + "Modified ACL for sub: {0} with users {1}.".format(sub_name, str(sub_authorized_users))) + except AmsException as ae: + LOGGER.error("Could not modify ACL for sub {0}.\nError: {1}".format(sub_name, str(ae))) + continue - LOGGER.critical("-----------------------------------------") + LOGGER.info("Service Type: " + srv_type) + LOGGER.critical("Missing DNS: " + str(missing_dns)) + LOGGER.critical("Not in production endpoints: " +str(not_in_production_endpoints)) + LOGGER.info("Total Users Created: " + str(user_count)) + LOGGER.info("Total Bindings Updated: " + str(update_binding_count)) + LOGGER.info("Updated bingings: " + str(update_bindings_names)) def main(args=None): diff --git a/bin/argo-api-authn-scripts/ams-create-users-gocdb.py b/bin/argo-api-authn-scripts/ams-create-users-gocdb.py index 78a7ef3..d8c148e 100755 --- a/bin/argo-api-authn-scripts/ams-create-users-gocdb.py +++ b/bin/argo-api-authn-scripts/ams-create-users-gocdb.py @@ -12,6 +12,7 @@ import ldap import re import urllib.parse +from argo_ams_library import ArgoMessagingService, AmsUser, AmsUserProject, AmsException, AmsServiceException # set up logging LOGGER = logging.getLogger("AMS User create script") @@ -20,6 +21,7 @@ "emailAddress", "CN", "OU", "O", "postalCode", "street", "L", "ST", "C", "DC" ] + class RdnSequence(object): def __init__(self, rdn_string): @@ -60,7 +62,6 @@ def _assign_rdn_to_field(self, rdn_type, rdn_value): Assign an RDN value to the correct field based on its type """ - if rdn_type == "emailAddress": self.EmailAddress.append(rdn_value) @@ -94,17 +95,17 @@ def _assign_rdn_to_field(self, rdn_type, rdn_value): @staticmethod def _escape_rdn_string(dn_string): """ - Method that checks and escapes any possible single slash characters in RDN values + Method that checks and escapes any possible single slash characters and commas in RDN values :param dn_string: :return: the escaped string """ re_match_key = re.compile("(\/\w*=)") - tokens = list(filter(None,re_match_key.split(dn_string))) - escaped_string = "".join(x.replace("/","\/") if not re_match_key.match(x) else x for x in tokens) + tokens = list(filter(None, re_match_key.split(dn_string))) + escaped_string = "".join(x.replace("/", "\/") if not re_match_key.match(x) else x for x in tokens) - return escaped_string + return escaped_string.replace(",", "\,") def _parse_dn_string_ldap_util(self, dn_string): """ @@ -124,7 +125,6 @@ def _parse_dn_string_ldap_util(self, dn_string): except Exception as e: raise ValueError(str(e)) - # A DN string with the value of /DC=org/DC=terena/DC=tcs/C=DE/O=hosts/O=GermanGrid/OU=DESY/CN=host/example.com # will produce the following rdns list # ['CN=host/example.com', 'OU=DESY', 'O=GermanGrid', 'O=hosts', 'C=DE', 'DC=tcs', 'DC=terena', 'DC=org'] @@ -300,7 +300,6 @@ def __str__(self): def create_users(config, verify): - # retrieve ams info ams_host = config.get("AMS", "ams_host") ams_project = config.get("AMS", "ams_project") @@ -322,6 +321,9 @@ def create_users(config, verify): # cert key tuple cert_creds = (config.get("AMS", "cert"), config.get("AMS", "cert_key")) + # init the Argo Messaging Service + ams = ArgoMessagingService(endpoint=ams_host, token=ams_token, project=ams_project) + # services holds all different services that the users might belong to(which translates to ams topics) # each service will have a list of users associated with it services = {} @@ -336,21 +338,23 @@ def create_users(config, verify): # form the goc db url goc_db_url = goc_db_url_arch.replace("{{service-type}}", srv_type) - LOGGER.info("\nAccessing url: " + goc_db_url) - LOGGER.info("\nStarted the process for service-type:" + srv_type) + LOGGER.info("Accessing url: " + goc_db_url) + LOGGER.info("Started the process for service-type: " + srv_type) # grab the xml data from goc db - goc_request = requests.get(url=goc_db_url, cert=cert_creds ,verify=False) + goc_request = requests.get(url=goc_db_url, cert=cert_creds, verify=False) LOGGER.info(goc_request.text) # users from goc db that don't have a dn registered missing_dns = [] + not_in_production_endpoints = [] + # updated bindings count - update_binding_count= 0 + update_binding_count = 0 # updated bindings names - update_bindings_names= [] + update_bindings_names = [] # srv_type srv_type = srv_type.replace(".", "-") @@ -371,6 +375,13 @@ def create_users(config, verify): # Create AMS user hostname = service_endpoint.find("HOSTNAME").text.replace(".", "-") sitename = service_endpoint.find("SITENAME").text.replace(".", "-") + + # check if the endpoint is in production + if service_endpoint.find("IN_PRODUCTION").text != "Y": + LOGGER.warning("Skipping not in production endpoint: " + hostname) + not_in_production_endpoints.append(hostname) + continue + user_binding_name = service_type + "---" + hostname + "---" + sitename # try to get the site's contact email @@ -400,14 +411,14 @@ def create_users(config, verify): site_contact_emails[site_name] = contact_email except Exception as e: - LOGGER.warning("Skipping endpoint {} under site {}, {}".format( - hostname, site_name, e)) + LOGGER.warning("Skipping endpoint {0} under site {1}, {2}".format( + hostname, site_name, str(e))) # convert the dn try: service_dn = RdnSequence(service_dn.text).__str__() except ValueError as ve: - LOGGER.error("Invalid DN: {}. Exception: {}".format(service_dn.text, ve)) + LOGGER.error("Invalid DN: {0}. Exception: {1}".format(service_dn.text, str(ve))) continue # check if the given DN already corresponds to a binding @@ -430,47 +441,47 @@ def create_users(config, verify): # else if the Dn isn't in use, go through the full process of creating or updating an existing binding elif binding_exists_req.status_code == 404: - project = {'project': ams_project, 'roles': [users_role]} - usr_create = {'projects': [project], 'email': contact_email} - - # create the user - ams_user_crt_url = 'https://{0}/v1/projects/{1}/members/{2}?key={3}'.format( - ams_host, ams_project, user_binding_name, ams_token) - ams_usr_crt_req = requests.post(url=ams_user_crt_url, data=json.dumps(usr_create), verify=verify) - LOGGER.info(ams_usr_crt_req.text) - ams_user_uuid = "" - # if the response is neither a 200(OK) nor a 409(already exists) - # then move on to the next user - if ams_usr_crt_req.status_code != 200 and ams_usr_crt_req.status_code != 409: - LOGGER.critical("\nUser: " + user_binding_name) - LOGGER.critical( - "\nSomething went wrong while creating ams user." + - "\nBody data: " + str(usr_create) + "\nResponse Body: " + - ams_usr_crt_req.text) - continue - - if ams_usr_crt_req.status_code == 200: - ams_user_uuid = ams_usr_crt_req.json()["uuid"] - # count how many users have been created + user_project = AmsUserProject( + project=ams_project, + roles=[users_role] + ) + user_create_data = AmsUser( + projects=[user_project], + email=contact_email, + name=user_binding_name + ) + + exists = False + try: + # create the user + created_ams_user = ams.create_user(user=user_create_data, verify=verify) + LOGGER.info("Created user: " + created_ams_user.name) + ams_user_uuid = created_ams_user.uuid user_count += 1 - - # If the user already exists, Get user by username - if ams_usr_crt_req.status_code == 409: - proj_member_list_url = "https://{0}/v1/projects/{1}/members/{2}?key={3}".format(ams_host, ams_project, user_binding_name, ams_token) - ams_usr_get_req = requests.get(url=proj_member_list_url, verify=verify) - - # if the user retrieval was ok - if ams_usr_get_req.status_code == 200: - LOGGER.info("\nSuccessfully retrieved user {} from ams".format(user_binding_name)) - ams_user_uuid = ams_usr_get_req.json()["uuid"] + except AmsException as e: + if isinstance(e, AmsServiceException) and e.code == 409: + exists = True else: - LOGGER.critical( - "\nCould not retrieve user {} from ams." - "\n Response {}".format(user_binding_name, ams_usr_get_req.text)) + LOGGER.error("User: " + user_binding_name) + LOGGER.error( + "Something went wrong while creating ams user." + + "\nError: " + str(e)) continue + # If the user already exists, Get user by username + if exists: + try: + ams_user_uuid = \ + ams.get_project_member(username=user_binding_name, + verify=verify).uuid + LOGGER.info("Successfully retrieved user: " + user_binding_name) + except AmsException as ae: + LOGGER.error( + "Could not retrieve user {0} from ams." + "\nError: {1}".format(user_binding_name, str(ae))) + continue # Create the respective AUTH binding bd_data = { @@ -481,33 +492,40 @@ def create_users(config, verify): "auth_type": "x509" } - create_binding_url = "https://{0}/v1/bindings/{1}?key={2}".format(authn_host, user_binding_name, authn_token) + create_binding_url = "https://{0}/v1/bindings/{1}?key={2}".format(authn_host, user_binding_name, + authn_token) authn_binding_crt_req = requests.post(url=create_binding_url, data=json.dumps(bd_data), verify=verify) LOGGER.info(authn_binding_crt_req.text) if authn_binding_crt_req.status_code != 201 and authn_binding_crt_req.status_code != 409: - LOGGER.critical("Something went wrong while creating a binding.\nBody data: " + str(bd_data) + "\nResponse: " + authn_binding_crt_req.text) + LOGGER.critical("Something went wrong while creating a binding.\nBody data: " + str( + bd_data) + "\nResponse: " + authn_binding_crt_req.text) continue # if the binding already exists, check for an updated DN from gocdb if authn_binding_crt_req.status_code == 409: - retrieve_binding_url = "https://{0}/v1/bindings/{1}?key={2}".format(authn_host, user_binding_name, authn_token) + retrieve_binding_url = "https://{0}/v1/bindings/{1}?key={2}".format(authn_host, user_binding_name, + authn_token) authn_ret_bind_req = requests.get(url=retrieve_binding_url, verify=verify) # if the binding retrieval was ok if authn_ret_bind_req.status_code == 200: - LOGGER.info("\nSuccessfully retrieved binding {} from authn. Checking for DN update.".format(user_binding_name)) + LOGGER.info("Successfully retrieved binding {0} from authn.Checking for DN update.".format( + user_binding_name)) binding = authn_ret_bind_req.json() # check if the dn has changed if binding["auth_identifier"] != service_dn: # update the respective binding with the new dn - bind_upd_req_url = "https://{0}/v1/bindings/{1}?key={2}".format(authn_host, user_binding_name, authn_token) + bind_upd_req_url = "https://{0}/v1/bindings/{1}?key={2}".format(authn_host, + user_binding_name, + authn_token) upd_bd_data = { "auth_identifier": service_dn } - authn_bind_upd_req = requests.put(url=bind_upd_req_url, data=json.dumps(upd_bd_data), verify=verify) + authn_bind_upd_req = requests.put(url=bind_upd_req_url, data=json.dumps(upd_bd_data), + verify=verify) LOGGER.info(authn_bind_upd_req.text) if authn_bind_upd_req.status_code == 200: update_binding_count += 1 @@ -515,70 +533,58 @@ def create_users(config, verify): else: LOGGER.critical( - "\nCould not retrieve binding {} from authn." - "\n Response {}".format(user_binding_name, authn_ret_bind_req.text)) + "\nCould not retrieve binding {0} from authn." + "\nResponse {1}".format(user_binding_name, authn_ret_bind_req.text)) continue - # add the user to the AMS project with corresponding role - add_user_project_url = "https://{0}/v1/projects/{1}/members/{2}:add?key={3}".format(ams_host, - ams_project, - user_binding_name, - ams_token) - - add_user_project_req_body = { - "project": ams_project, - "roles": [users_role] - } - - LOGGER.info("Adding user {0} to project {1} . . .".format(user_binding_name, ams_project)) - - add_user_project_req = requests.post(url=add_user_project_url, - data=json.dumps(add_user_project_req_body), verify=verify) - - if add_user_project_req.status_code != 200 and add_user_project_req.status_code != 409 : - LOGGER.info("Could not add user {0} to project {1}.\nResponse {2}".format(user_binding_name, - ams_project, - add_user_project_req.text)) - continue + try: + ams.add_project_member(username=user_binding_name, + roles=[users_role], + verify=verify) + except AmsException as e: + if isinstance(e, AmsServiceException) and e.code == 409: + pass + else: + LOGGER.error("Could not add user {0} to project {1}.\nError: {2}".format(user_binding_name, + ams_project, + str(e))) + continue # if both the user and binding have been created, assign the user to the acl of the topic services[service_type].append(user_binding_name) LOGGER.info("Marked user {0} to be added to the {1} topic under the {2} project".format( user_binding_name, service_type, ams_project)) - # modify the acl for each topic , to add all associated users authorized_users = services[srv_type] if len(authorized_users) != 0: - - get_topic_acl_req = requests.get("https://"+ams_host+"/v1/projects/"+ams_project+"/topics/"+srv_type+":acl?key="+ams_token, verify=verify) - - if get_topic_acl_req.status_code == 200: - acl_users = json.loads(get_topic_acl_req.text) - authorized_users = authorized_users + acl_users["authorized_users"] + try: + acl = ams.getacl_topic(topic=srv_type, verify=verify) # remove duplicates - authorized_users = list(set(authorized_users)) - modify_topic_acl_req = requests.post("https://"+ams_host+"/v1/projects/"+ams_project+"/topics/"+srv_type+":modifyAcl?key="+ams_token, data=json.dumps({'authorized_users': authorized_users}), verify=verify) - LOGGER.critical("Modified ACL for topic: {} with users {}. Response from AMS {}".format(srv_type, str(authorized_users), modify_topic_acl_req.text)) - else: - LOGGER.critical("Couldn't get ACL for topic {}. Response from AMS {}".format(srv_type, get_topic_acl_req.text)) + authorized_users = list(set(authorized_users + acl["authorized_users"])) + try: + ams.modifyacl_topic(topic=srv_type, users=authorized_users, verify=verify) + LOGGER.info("Modified ACL for topic: {0} with users {1}.".format(srv_type, str(authorized_users))) + except AmsException as ae: + LOGGER.error("Could not modify ACL for topic {0}.\nError: {1}".format(srv_type, str(ae))) + except AmsException as ae: + LOGGER.error("Couldn't get ACL for topic {0}.\nError: {1}".format(srv_type, str(ae))) - LOGGER.critical("Service Type: " + srv_type) + LOGGER.info("Service Type: " + srv_type) LOGGER.critical("Missing DNS: " + str(missing_dns)) - LOGGER.critical("Total Users Created: " + str(user_count)) - - LOGGER.critical("Total Bindings Updated: " + str(update_binding_count)) - - LOGGER.critical("Updated bindings: " + str(update_bindings_names)) + LOGGER.critical("Not in production endpoints: " + str(not_in_production_endpoints)) - LOGGER.critical("-----------------------------------------") + LOGGER.info("Total Users Created: " + str(user_count)) + LOGGER.info("Total Bindings Updated: " + str(update_binding_count)) + + LOGGER.info("Updated bindings: " + str(update_bindings_names)) -def main(args=None): +def main(args=None): # set up the config parser config = configparser.ConfigParser() @@ -609,11 +615,11 @@ def main(args=None): if __name__ == "__main__": - - parser = argparse.ArgumentParser(description="Create ams users and their respective bindings using data imported from goc db") + parser = argparse.ArgumentParser( + description="Create ams users and their respective bindings using data imported from goc db") parser.add_argument( "-c", "--ConfigPath", type=str, help="Path for the config file") parser.add_argument( - "-verify", "--Verify", help="SSL verification for requests", action="store_true") + "-verify", "--Verify", help="SSL verification for requests", action="store_true") - sys.exit(main(parser.parse_args())) \ No newline at end of file + sys.exit(main(parser.parse_args())) diff --git a/bin/requirements.txt b/bin/requirements.txt index dae2ebe..faf6bdc 100644 --- a/bin/requirements.txt +++ b/bin/requirements.txt @@ -1,3 +1,4 @@ -defusedxml==0.5.0 -requests==2.20 +defusedxml==0.7.1 python-ldap==3.4.0 +argo-ams-library==0.5.9 +requests diff --git a/bindings/binding.go b/bindings/binding.go index 03291ba..582d9da 100644 --- a/bindings/binding.go +++ b/bindings/binding.go @@ -17,6 +17,7 @@ type Binding struct { UniqueKey string `json:"unique_key" required:"true"` AuthType string `json:"auth_type" required:"true"` CreatedOn string `json:"created_on,omitempty"` + UpdatedOn string `json:"updated_on,omitempty"` LastAuth string `json:"last_auth,omitempty"` } @@ -305,6 +306,8 @@ func UpdateBinding(original Binding, tempBind TempUpdateBinding, store stores.St return Binding{}, err } + updated.UpdatedOn = utils.ZuluTimeNow() + // convert the updated binding to a QBinding if err := utils.CopyFields(updated, &qUpdatedBinding); err != nil { err = utils.APIGenericInternalError(err.Error()) diff --git a/conf/argo-api-authn-config.template b/conf/argo-api-authn-config.template index 0c38402..f121389 100644 --- a/conf/argo-api-authn-config.template +++ b/conf/argo-api-authn-config.template @@ -7,12 +7,12 @@ "certificate_key":"/etc/pki/tls/private/localhost.key", "service_token": "agelos", "supported_auth_types": ["x509", "oidc"], - "supported_auth_methods": ["api-key", "x-api-token"], + "supported_auth_methods": ["api-key", "headers"], "supported_service_types": ["ams", "web-api", "custom"], "verify_ssl": false, "trust_unknown_cas": true, "verify_certificate": false, - "service_types_paths": {"ams": "/v1/users:byUUID/{{identifier}}?key={{access_key}}"}, + "service_types_paths": {"ams": "/v1/users:byUUID/{{identifier}}"}, "service_types_retrieval_fields": {"ams": "token"}, "syslog_enabled": false, "client_cert_host_verification" : false diff --git a/config.json b/config.json index df4bfd8..ffe2384 100644 --- a/config.json +++ b/config.json @@ -13,7 +13,7 @@ "trust_unknown_cas": false, "verify_certificate": true, "service_types_paths": { - "ams": "/v1/users:byUUID/{{identifier}}?key={{access_key}}", + "ams": "/v1/users:byUUID/{{identifier}}}", "web-api": "/api/v2/admin/users:byID/{{identifier}}?export=flat" }, "service_types_retrieval_fields": { diff --git a/config/config_test.go b/config/config_test.go index 675a483..62ab326 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -35,7 +35,7 @@ func (suite *ConfigTestSuite) TestConfigSetUp() { TrustUnknownCAs: false, VerifyCertificate: true, ServiceTypesPaths: map[string]string{ - "ams": "/v1/users:byUUID/{{identifier}}?key={{access_key}}", + "ams": "/v1/users:byUUID/{{identifier}}", "web-api": "/api/v2/admin/users:byID/{{identifier}}?export=flat", }, ServiceTypesRetrievalFields: map[string]string{ diff --git a/config/configuration-test-files/test-conf.json b/config/configuration-test-files/test-conf.json index d7f02ff..6d7018c 100644 --- a/config/configuration-test-files/test-conf.json +++ b/config/configuration-test-files/test-conf.json @@ -13,7 +13,7 @@ "trust_unknown_cas": false, "verify_certificate": true, "service_types_paths": { - "ams": "/v1/users:byUUID/{{identifier}}?key={{access_key}}", + "ams": "/v1/users:byUUID/{{identifier}}", "web-api": "/api/v2/admin/users:byID/{{identifier}}?export=flat" }, "service_types_retrieval_fields": { diff --git a/docs/swagger.yml b/docs/swagger.yml index 4548ddc..8011852 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -683,6 +683,10 @@ definitions: description: the type of authentication that this binding represets(e.g. x509) created_on: type: string + description: when the binding was created + updated_on: + type: string + description: when the binding was updated last_auth: type: string @@ -722,8 +726,10 @@ definitions: description: service type's type, either AMS or web-api created_on: type: string - enum: [ams, web-api] description: datetime of creation + updated_on: + type: string + description: when the servcie type was updated AuthMethods: type: object @@ -763,6 +769,9 @@ definitions: created_on: type: string description: when the auth method was created + updated_on: + type: string + description: when the auth method was updated TokenResponse: diff --git a/docs/v1/docs/api_authmethods.md b/docs/v1/docs/api_authmethods.md index 8fdcfbe..7e162de 100644 --- a/docs/v1/docs/api_authmethods.md +++ b/docs/v1/docs/api_authmethods.md @@ -80,7 +80,10 @@ Success Response ``` { - "access_key": "key1", + "headers": { + "header-1": "value-1", + "header-2": "value-2" + }, "host": "127.0.0.1", "service_uuid": "da22b2d4-ba6c-43ca-b28d-400sd0a5d83e", "port": 9000, @@ -121,7 +124,8 @@ If the request is successful, the response contains information for the requeste "port": 9000, "type": "api-key", "uuid": "da22b2d4-8ip0-43ca-b28d-500sd0a5d876e", - "created_on": "2018-05-05T18:04:05Z" + "created_on": "2018-05-05T18:04:05Z", + "updated_on": "2021-05-05T18:04:05Z" } ``` Please refer to section [Errors](api_errors.md) to see all possible Errors @@ -157,7 +161,8 @@ If the request is successful, the response contains information for all the auth "port": 9000, "type": "api-key", "uuid": "da22b2d4-8ip0-43ca-b28d-500sd0a5d876e", - "created_on": "2018-05-05T18:04:05Z" + "created_on": "2018-05-05T18:04:05Z", + "updated_on": "2021-05-05T18:04:05Z" }, { "access_key": "key1", @@ -166,7 +171,8 @@ If the request is successful, the response contains information for all the auth "port": 9000, "type": "api-key", "uuid": "da22b2d4-9kl2-43ca-b28d-500sd0a5d876e", - "created_on": "2018-05-05T18:04:05Z" + "created_on": "2018-05-05T18:04:05Z", + "updated_on": "2021-05-05T18:04:05Z" } ] } @@ -214,7 +220,8 @@ Success Response "port": 8080, "type": "api-key", "uuid": "da22b2d4-8ip0-43ca-b28d-500sd0a5d876e", - "created_on": "2018-05-05T18:04:05Z" + "created_on": "2018-05-05T18:04:05Z", + "updated_on": "2021-05-05T18:04:05Z" } ``` diff --git a/docs/v1/docs/api_bindings.md b/docs/v1/docs/api_bindings.md index d95727c..4884ead 100644 --- a/docs/v1/docs/api_bindings.md +++ b/docs/v1/docs/api_bindings.md @@ -99,6 +99,7 @@ If the request is successful, the response contains all the bindings in the serv "unique_key": "key", "auth_type": "x509", "created_on": "2018-05-23T09:25:25Z", + "updated_on": "2021-05-05T18:04:05Z" "last_auth": "2018-05-23T09:25:25Z" }, { @@ -161,6 +162,7 @@ If the request is successful, the response contains all the bindings under the g "unique_key": "key", "auth_type": "x509", "created_on": "2018-05-23T09:25:43Z", + "updated_on": "2021-05-05T18:04:05Z" "last_auth": "2018-05-23T09:25:25Z" } ] @@ -287,7 +289,8 @@ If the request is successful, the response contains the updated binding. "auth_identifier": "host1", "unique_key": "key", "auth_type": "x509", - "created_on": "2018-05-24T09:58:17Z" + "created_on": "2018-05-24T09:58:17Z", + "updated_on": "2021-05-05T18:04:05Z" } ``` diff --git a/docs/v1/docs/api_service_types.md b/docs/v1/docs/api_service_types.md index acb5276..c1a872e 100644 --- a/docs/v1/docs/api_service_types.md +++ b/docs/v1/docs/api_service_types.md @@ -105,7 +105,8 @@ GET/v1/service-types "auth_method": "api-key", "uuid": "da22b2d4-ba6c-43ca-b28d-400sd0a5d83e", "type": "ams", - "created_on": "2018-05-13T21:52:58Z" + "created_on": "2018-05-13T21:52:58Z", + "updated_on": "2021-05-05T18:04:05Z" } ] } @@ -137,7 +138,8 @@ If the request is successful, the response contains information for the requeste "auth_method": "api-key", "uuid": "da22b2d4-ba6c-43ca-b28d-400cd0a5d83e", "type": "ams", - "created_on": "2018-05-05T18:04:05Z" + "created_on": "2018-05-05T18:04:05Z", + "updated_on": "2021-05-05T18:04:05Z" } ``` @@ -180,7 +182,8 @@ If the request is successful, the response contains the updated service type. "auth_method": "api-key", "uuid": "da22b2d4-ba6c-43ca-b28d-400cd0a5d83e", "type": "ams", - "created_on": "2018-05-05T18:04:05Z" + "created_on": "2018-05-05T18:04:05Z", + "updated_on": "2021-05-05T18:04:05Z" } ``` diff --git a/handlers/authmethods_handlers_test.go b/handlers/authmethods_handlers_test.go index 5f263c7..2f65315 100644 --- a/handlers/authmethods_handlers_test.go +++ b/handlers/authmethods_handlers_test.go @@ -6,12 +6,15 @@ import ( "github.com/ARGOeu/argo-api-authn/authmethods" "github.com/ARGOeu/argo-api-authn/config" "github.com/ARGOeu/argo-api-authn/stores" + "github.com/ARGOeu/argo-api-authn/utils" "github.com/gorilla/mux" LOGGER "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" "net/http" "net/http/httptest" + "strings" "testing" + "time" ) type AuthMethodsHandlersTestSuite struct { @@ -793,6 +796,7 @@ func (suite *AuthMethodsHandlersTestSuite) TestAuthMethodUpdateOne() { "type": "api-key", "uuid": "am_uuid_1", "created_on": "", + "updated_on": "{{UPDATED_ON}}", "access_key": "key1" }` @@ -811,6 +815,12 @@ func (suite *AuthMethodsHandlersTestSuite) TestAuthMethodUpdateOne() { w := httptest.NewRecorder() router.HandleFunc("/service-types/{service-type}/hosts/{host}/authm", WrapConfig(AuthMethodUpdateOne, mockstore, cfg)) router.ServeHTTP(w, req) + + amU, _ := mockstore.QueryApiKeyAuthMethods("uuid1", "host1") + expRespJSON = strings.Replace(expRespJSON, "{{UPDATED_ON}}", amU[0].UpdatedOn, 1) + // make sure the updated time is before now + updatedTime, _ := time.Parse(utils.ZULU_FORM, amU[0].UpdatedOn) + suite.True(updatedTime.Before(time.Now().UTC())) suite.Equal(200, w.Code) suite.Equal(expRespJSON, w.Body.String()) } @@ -831,6 +841,7 @@ func (suite *AuthMethodsHandlersTestSuite) TestAuthMethodUpdateOneIllegalFields( "type": "api-key", "uuid": "am_uuid_1", "created_on": "", + "updated_on": "{{UPDATED_ON}}", "access_key": "access_key" }` @@ -849,6 +860,12 @@ func (suite *AuthMethodsHandlersTestSuite) TestAuthMethodUpdateOneIllegalFields( w := httptest.NewRecorder() router.HandleFunc("/service-types/{service-type}/hosts/{host}/authm", WrapConfig(AuthMethodUpdateOne, mockstore, cfg)) router.ServeHTTP(w, req) + + amU, _ := mockstore.QueryApiKeyAuthMethods("uuid1", "host1") + expRespJSON = strings.Replace(expRespJSON, "{{UPDATED_ON}}", amU[0].UpdatedOn, 1) + // make sure the updated time is before now + updatedTime, _ := time.Parse(utils.ZULU_FORM, amU[0].UpdatedOn) + suite.True(updatedTime.Before(time.Now().UTC())) suite.Equal(200, w.Code) suite.Equal(expRespJSON, w.Body.String()) } diff --git a/handlers/binding_handlers_test.go b/handlers/binding_handlers_test.go index d52bac9..86fab93 100644 --- a/handlers/binding_handlers_test.go +++ b/handlers/binding_handlers_test.go @@ -3,10 +3,13 @@ package handlers import ( "bytes" "github.com/ARGOeu/argo-api-authn/stores" + "github.com/ARGOeu/argo-api-authn/utils" LOGGER "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" "net/http" + "strings" "testing" + "time" "encoding/json" "github.com/ARGOeu/argo-api-authn/bindings" @@ -865,7 +868,8 @@ func (suite *BindingHandlersSuite) TestBindingUpdate() { "auth_identifier": "test_dn_1", "unique_key": "unique_key_1", "auth_type": "x509", - "created_on": "2018-05-05T15:04:05Z" + "created_on": "2018-05-05T15:04:05Z", + "updated_on": "{{UPDATED_ON}}" }` req, err := http.NewRequest("PUT", "http://localhost:8080/bindings/b1", bytes.NewBuffer([]byte(postJSON))) if err != nil { @@ -882,6 +886,13 @@ func (suite *BindingHandlersSuite) TestBindingUpdate() { w := httptest.NewRecorder() router.HandleFunc("/bindings/{name}", WrapConfig(BindingUpdate, mockstore, cfg)) router.ServeHTTP(w, req) + + qB, _ := mockstore.QueryBindingsByUUIDAndName("b_uuid1", "updated_name") + expRespJSON = strings.Replace(expRespJSON, "{{UPDATED_ON}}", qB[0].UpdatedOn, 1) + // make sure the updated time is before now + updatedTime, _ := time.Parse(utils.ZULU_FORM, qB[0].UpdatedOn) + suite.True(updatedTime.Before(time.Now().UTC())) + suite.Equal(200, w.Code) suite.Equal(expRespJSON, w.Body.String()) } diff --git a/handlers/service_type_handlers_test.go b/handlers/service_type_handlers_test.go index a2cfc0c..71cd343 100644 --- a/handlers/service_type_handlers_test.go +++ b/handlers/service_type_handlers_test.go @@ -2,10 +2,13 @@ package handlers import ( "bytes" + "github.com/ARGOeu/argo-api-authn/utils" LOGGER "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" "net/http" + "strings" "testing" + "time" "encoding/json" "github.com/ARGOeu/argo-api-authn/config" @@ -676,6 +679,7 @@ func (suite *BindingHandlersSuite) TestServiceTypeUpdate() { "auth_method": "api-key", "uuid": "uuid1", "created_on": "2018-05-05T18:04:05Z", + "updated_on": "{{UPDATED_ON}}", "type": "ams" }` req, err := http.NewRequest("PUT", "http://localhost:8080/service-types/s1", bytes.NewBuffer([]byte(postJSON))) @@ -693,6 +697,12 @@ func (suite *BindingHandlersSuite) TestServiceTypeUpdate() { w := httptest.NewRecorder() router.HandleFunc("/service-types/{service-type}", WrapConfig(ServiceTypeUpdate, mockstore, cfg)) router.ServeHTTP(w, req) + + qSt, _ := mockstore.QueryServiceTypes("updated_name") + expRespJSON = strings.Replace(expRespJSON, "{{UPDATED_ON}}", qSt[0].UpdatedOn, 1) + // make sure the updated time is before now + updatedTime, _ := time.Parse(utils.ZULU_FORM, qSt[0].UpdatedOn) + suite.True(updatedTime.Before(time.Now().UTC())) suite.Equal(200, w.Code) suite.Equal(expRespJSON, w.Body.String()) } diff --git a/main.go b/main.go index 69b53f9..e00058f 100644 --- a/main.go +++ b/main.go @@ -47,7 +47,7 @@ func main() { // configure the TLS config for the server tlsConfig := &tls.Config{ - MinVersion: tls.VersionTLS10, + MinVersion: tls.VersionTLS12, ClientAuth: cfg.ClientAuthPolicy(), ClientCAs: auth.LoadCAs(cfg.CertificateAuthorities), } diff --git a/postman/authn_ci-cd_tests.postman_collection.json b/postman/authn_ci-cd_tests.postman_collection.json index 4f29c7e..9923293 100644 --- a/postman/authn_ci-cd_tests.postman_collection.json +++ b/postman/authn_ci-cd_tests.postman_collection.json @@ -66,10 +66,10 @@ { "listen": "test", "script": { - "id": "c95d2c5c-937d-4df0-873d-f6fd9e35de9b", + "id": "edf1b517-d398-4834-8de2-50bf840c2383", "type": "text/javascript", "exec": [ - "pm.test(\"Check that the Servie-type was successfully created\", function(){", + "pm.test(\"Check that the Service-type was successfully created\", function(){", " pm.response.to.have.status(201) ", "});", "", @@ -89,7 +89,7 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"name\": \"ams-devel-cicd\",\n\t\"hosts\": [\"{{ams-host}}\"],\n\t\"auth_types\": [\"x509\"],\n\t\"auth_method\": \"api-key\",\n\t\"type\": \"ams\"\n}" + "raw": "{\n\t\"name\": \"ams-devel-cicd\",\n\t\"hosts\": [\"{{ams-host}}\"],\n\t\"auth_types\": [\"x509\"],\n\t\"auth_method\": \"headers\",\n\t\"type\": \"ams\"\n}" }, "url": { "raw": "https://{{authn-host}}/v1/service-types?key={{authn-token}}", @@ -137,7 +137,7 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"port\": 443,\n\t\"host\": \"{{ams-host}}\",\n\t\"access_key\": \"{{ams-token}}\"\n}" + "raw": "{\n\t\"port\": 443,\n\t\"host\": \"{{ams-host}}\",\n\t\"headers\": {\n\t\t\"x-api-key\": \"{{ams-token}}\"\n\t}\n}" }, "url": { "raw": "https://{{authn-host}}/v1/service-types/ams-devel-cicd/authm?key={{authn-token}}", @@ -187,7 +187,7 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"service_uuid\": \"{{st-uuid}}\",\n\t\"host\": \"{{ams-host}}\",\n\t\"auth_identifier\": \"CN=jenkins.einfra.grnet.gr,OU=E-Infrastructures Department,O=National Infrastructures for Research and Technology (GRNET),L=Athens,C=GR\",\n\t\"unique_key\": \"3530bde2-a8bc-417b-9f8e-c36dedeedafb\",\n\t\"auth_type\": \"x509\"\n}\n" + "raw": "{\n\t\"service_uuid\": \"{{st-uuid}}\",\n\t\"host\": \"{{ams-host}}\",\n\t\"auth_identifier\": \"CN=*.einfra.grnet.gr,OU=GRNET,O=National Infrastructures for Research and Technology,ST=Attikí,C=GR\",\n\t\"unique_key\": \"3530bde2-a8bc-417b-9f8e-c36dedeedafb\",\n\t\"auth_type\": \"x509\"\n}\n" }, "url": { "raw": "https://{{authn-host}}/v1/bindings/authn-cicd?key={{authn-token}}", diff --git a/routing/routing.go b/routing/routing.go index 8d455ee..c63ed62 100644 --- a/routing/routing.go +++ b/routing/routing.go @@ -69,7 +69,7 @@ func NewRouting(routes []APIRoute, store stores.Store, config *config.Config) *A } var ApiRoutes = []APIRoute{ - {"serviceTypes:create", "POST", "/service-types", handlers.ServiceTypeCreate, true}, + {"serviceTypes:Create", "POST", "/service-types", handlers.ServiceTypeCreate, true}, {"serviceTypes:ListOne", "GET", "/service-types/{service-type}", handlers.ServiceTypesListOne, true}, {"serviceTypes:DeleteOne", "DELETE", "/service-types/{service-type}", handlers.ServiceTypeDeleteOne, true}, {"serviceTypes:ListOne", "PUT", "/service-types/{service-type}", handlers.ServiceTypeUpdate, true}, @@ -77,13 +77,13 @@ var ApiRoutes = []APIRoute{ {"authMethod:Create", "POST", "/service-types/{service-type}/authm", handlers.AuthMethodCreate, true}, {"authMethod:ListOne", "GET", "/service-types/{service-type}/hosts/{host}/authm", handlers.AuthMethodListOne, true}, {"authMethod:Delete", "DELETE", "/service-types/{service-type}/hosts/{host}/authm", handlers.AuthMethodDeleteOne, true}, - {"authMethod:Delete", "PUT", "/service-types/{service-type}/hosts/{host}/authm", handlers.AuthMethodUpdateOne, true}, + {"authMethod:Update", "PUT", "/service-types/{service-type}/hosts/{host}/authm", handlers.AuthMethodUpdateOne, true}, {"bindings:ListAllByServiceTypeAndHost", "GET", "/service-types/{service-type}/hosts/{host}/bindings", handlers.BindingListAllByServiceTypeAndHost, true}, {"authMethod:ListAll", "GET", "/authm", handlers.AuthMethodListAll, true}, - {"bindings:create", "POST", "/bindings/{name}", handlers.BindingCreate, true}, + {"bindings:Create", "POST", "/bindings/{name}", handlers.BindingCreate, true}, {"bindings:ListAll", "GET", "/bindings", handlers.BindingListAll, true}, - {"bindings:update", "PUT", "/bindings/{name}", handlers.BindingUpdate, true}, + {"bindings:Update", "PUT", "/bindings/{name}", handlers.BindingUpdate, true}, {"bindings:ListOneByName", "GET", "/bindings/{name}", handlers.BindingListOneByName, true}, - {"bindings:delete", "DELETE", "/bindings/{name}", handlers.BindingDelete, true}, - {"auth:dn", "GET", "/service-types/{service-type}/hosts/{host}:authx509", handlers.AuthViaCert, false}, + {"bindings:Delete", "DELETE", "/bindings/{name}", handlers.BindingDelete, true}, + {"auth:Map", "GET", "/service-types/{service-type}/hosts/{host}:authx509", handlers.AuthViaCert, false}, } diff --git a/servicetypes/service_type.go b/servicetypes/service_type.go index 20b239a..b653673 100644 --- a/servicetypes/service_type.go +++ b/servicetypes/service_type.go @@ -15,6 +15,7 @@ type ServiceType struct { AuthMethod string `json:"auth_method" required:"true"` UUID string `json:"uuid"` CreatedOn string `json:"created_on"` + UpdatedOn string `json:"updated_on,omitempty"` Type string `json:"type" required:"true"` } @@ -320,6 +321,8 @@ func UpdateServiceType(original ServiceType, tempServiceType TempServiceType, st } } + updated.UpdatedOn = utils.ZuluTimeNow() + // convert the original service type to a QServiceType if err := utils.CopyFields(original, &qOriginalSt); err != nil { err = utils.APIGenericInternalError(err.Error()) diff --git a/servicetypes/service_type_test.go b/servicetypes/service_type_test.go index 208a52a..531688e 100644 --- a/servicetypes/service_type_test.go +++ b/servicetypes/service_type_test.go @@ -20,55 +20,55 @@ func (suite *ServiceTestSuite) TestCreateServiceType() { _ = cfg.ConfigSetUp("../config/configuration-test-files/test-conf.json") // test the normal case with type ams - s1 := ServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "ams"} + s1 := ServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "", "ams"} _, err := CreateServiceType(s1, mockstore, *cfg) res1, _ := mockstore.QueryServiceTypes("sCr") // test the normal case with type web-api - sWb := ServiceType{"sCr_wb", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "web-api"} + sWb := ServiceType{"sCr_wb", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "", "web-api"} _, errWb := CreateServiceType(sWb, mockstore, *cfg) res2, _ := mockstore.QueryServiceTypes("sCr_wb") // test the normal case with type custom - sCustom := ServiceType{"sCr_custom", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "token", "custom"} + sCustom := ServiceType{"sCr_custom", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "token", "", "custom"} _, errCustom := CreateServiceType(sCustom, mockstore, *cfg) res3, _ := mockstore.QueryServiceTypes("sCr_custom") // test the case where the name already exists - s2 := ServiceType{"s1", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "some_uuid", "", "ams"} + s2 := ServiceType{"s1", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "some_uuid", "", "", "ams"} _, err2 := CreateServiceType(s2, mockstore, *cfg) // test the case of unsupported auth type - s3 := ServiceType{"sCr", []string{"host1", "host2"}, []string{"unsup_type", "oidc"}, "api-key", "some_uuid", "", "ams"} + s3 := ServiceType{"sCr", []string{"host1", "host2"}, []string{"unsup_type", "oidc"}, "api-key", "some_uuid", "", "", "ams"} _, err3 := CreateServiceType(s3, mockstore, *cfg) // test the case of empty auth type list - s4 := ServiceType{"sCr", []string{"host1", "host2"}, []string{}, "api-key", "some_uuid", "", "ams"} + s4 := ServiceType{"sCr", []string{"host1", "host2"}, []string{}, "api-key", "some_uuid", "", "", "ams"} _, err4 := CreateServiceType(s4, mockstore, *cfg) // test the case of unsupported auth method - s5 := ServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "unsup_method", "some_uuid", "", "ams"} + s5 := ServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "unsup_method", "some_uuid", "", "", "ams"} _, err5 := CreateServiceType(s5, mockstore, *cfg) // test the case of empty name - s6 := ServiceType{"", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "ams"} + s6 := ServiceType{"", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "", "ams"} _, err6 := CreateServiceType(s6, mockstore, *cfg) // test the case of empty auth method - s8 := ServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "", "uuid1", "", "ams"} + s8 := ServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "", "uuid1", "", "", "ams"} _, err8 := CreateServiceType(s8, mockstore, *cfg) // test the case of empty hosts - s9 := ServiceType{"sCr", []string{}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "ams"} + s9 := ServiceType{"sCr", []string{}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "", "ams"} _, err9 := CreateServiceType(s9, mockstore, *cfg) // test the case of empty type - s10 := ServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", ""} + s10 := ServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "", ""} _, err10 := CreateServiceType(s10, mockstore, *cfg) // test the case of unsupported type type - s11 := ServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "unsup_type"} + s11 := ServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "", "unsup_type"} _, err11 := CreateServiceType(s11, mockstore, *cfg) suite.Equal(s1.Name, res1[0].Name) @@ -107,7 +107,7 @@ func (suite *ServiceTestSuite) TestFindServiceTypeByName() { mockstore.SetUp() // normal case - expS1 := ServiceType{"s1", []string{"host1", "host2", "host3"}, []string{"x509", "oidc"}, "api-key", "uuid1", "2018-05-05T18:04:05Z", "ams"} + expS1 := ServiceType{"s1", []string{"host1", "host2", "host3"}, []string{"x509", "oidc"}, "api-key", "uuid1", "2018-05-05T18:04:05Z", "", "ams"} ser1, err1 := FindServiceTypeByName("s1", mockstore) // not found case @@ -133,7 +133,7 @@ func (suite *ServiceTestSuite) TestFindServiceTypeByUUID() { mockstore.SetUp() // normal case - expS1 := ServiceType{"s1", []string{"host1", "host2", "host3"}, []string{"x509", "oidc"}, "api-key", "uuid1", "2018-05-05T18:04:05Z", "ams"} + expS1 := ServiceType{"s1", []string{"host1", "host2", "host3"}, []string{"x509", "oidc"}, "api-key", "uuid1", "2018-05-05T18:04:05Z", "", "ams"} ser1, err1 := FindServiceTypeByUUID("uuid1", mockstore) // not found case @@ -218,8 +218,8 @@ func (suite *ServiceTestSuite) TestUpdateService() { _ = cfg.ConfigSetUp("../config/configuration-test-files/test-conf.json") // original service type - qOriginal := stores.QServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "ams"} - original := ServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "ams"} + qOriginal := stores.QServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "", "ams"} + original := ServiceType{"sCr", []string{"host1", "host2"}, []string{"x509", "oidc"}, "api-key", "uuid1", "", "", "ams"} mockstore.ServiceTypes = append(mockstore.ServiceTypes, qOriginal) // test the normal case diff --git a/setup.py b/setup.py index 9cc1ebc..c9d7ef6 100644 --- a/setup.py +++ b/setup.py @@ -13,5 +13,5 @@ './bin/argo-api-authn-scripts/ams-create-users-cloud-info.py'], package_dir={'argo_api_authn_scripts': './bin/argo-api-authn-scripts/'}, packages=['argo_api_authn_scripts'], - install_requires=['defusedxml==0.5.0', 'requests==2.20', 'python-ldap==3.4.0'] + install_requires=['defusedxml==0.7.1', 'python-ldap==3.4.0', 'argo-ams-library==0.5.9', 'requests'] ) diff --git a/stores/models.go b/stores/models.go index 72d786b..af77f03 100644 --- a/stores/models.go +++ b/stores/models.go @@ -12,6 +12,7 @@ type QServiceType struct { AuthMethod string `json:"auth_method" bson:"auth_method"` UUID string `json:"uuid" bson:"uuid"` CreatedOn string `json:"created_on,omitempty" bson:"created_on,omitempty"` + UpdatedOn string `json:"updated_on,omitempty" bson:"updated_on,omitempty"` Type string `json:"type" bson:"type"` } @@ -24,6 +25,7 @@ type QBinding struct { AuthType string `json:"auth_type" bson:"auth_type"` UniqueKey string `json:"unique_key,omitempty"` CreatedOn string `json:"created_on,omitempty" bson:"created_on,omitempty"` + UpdatedOn string `json:"updated_on,omitempty" bson:"updated_on,omitempty"` LastAuth string `json:"last_auth,omitempty" bson:"last_auth,omitempty"` } @@ -36,6 +38,7 @@ type QBasicAuthMethod struct { Type string `json:"type" bson:"type"` UUID string `json:"uuid" bson:"uuid"` CreatedOn string `json:"created_on" bson:"created_on"` + UpdatedOn string `json:"updated_on,omitempty" bson:"updated_on,omitempty"` } type QApiKeyAuthMethod struct { diff --git a/stores/mongo.go b/stores/mongo.go index 9ac0ff7..0bf1ee1 100644 --- a/stores/mongo.go +++ b/stores/mongo.go @@ -400,7 +400,6 @@ func (mongo *MongoStore) UpdateAuthMethod(original QAuthMethod, updated QAuthMet db := mongo.Session.DB(mongo.Database) c := db.C("auth_methods") - if err := c.Update(original, updated); err != nil { log.WithFields( log.Fields{