def get_auth_url(self, state=None, redirect_uri=None): """ Return url which end-user should visit to authorize at ESIA. :param str or None state: identifier, will be returned as GET parameter in redirected request after auth. :param str or None redirect_uri: uri, where browser will be redirected after authorization. :return: url :rtype: str """ params = { 'client_id': self.settings.esia_client_id, 'client_secret': '', 'redirect_uri': redirect_uri or self.settings.redirect_uri, 'scope': self.settings.esia_scope, 'response_type': 'code', 'state': state or str(uuid.uuid4()), 'timestamp': get_timestamp(), 'access_type': 'offline' } params = sign_params(params, certificate_file=self.settings.certificate_file, private_key_file=self.settings.private_key_file) params = urlencode(sorted(params.items( ))) # sorted needed to make uri deterministic for tests. return '{base_url}{auth_url}?{params}'.format( base_url=self.settings.esia_service_url, auth_url=self._AUTHORIZATION_URL, params=params)
def get_auth_url(self, state=None): """ Return url which end-user should visit to authorize at ESIA. :param str or None state: identifier, will be returned as GET parameter in redirected request after auth.. :return: url :rtype: str """ params = { 'client_id': self.settings.esia_client_id, 'client_secret': '', 'redirect_uri': self.settings.redirect_uri, 'scope': self.settings.esia_scope, 'response_type': 'code', 'state': state or str(uuid.uuid4()), 'timestamp': get_timestamp(), 'access_type': 'offline' } params = sign_params(params, certificate_file=self.settings.certificate_file, private_key_file=self.settings.private_key_file) params = urlencode(sorted(params.items())) # sorted needed to make uri deterministic for tests. return '{base_url}{auth_url}?{params}'.format(base_url=self.settings.esia_service_url, auth_url=self._AUTHORIZATION_URL, params=params)
def test_sign_params(self): scope = 'openid http://esia.gosuslugi.ru/usr_inf' timestamp = '2015.11.02 09:37:16 +0000' client_id = 'YOURID' state = 'f918e3be-664e-45db-b5a2-adbf5d6ae3f6' params = { 'redirect_uri': 'http://your-service.ru/redirect_handler', 'access_type': 'offline', 'state': state, 'scope': scope, 'response_type': 'code', 'client_id': client_id, 'timestamp': timestamp, } signed_params = sign_params(params, certificate_file=TEST_SETTINGS.certificate_file, private_key_file=TEST_SETTINGS.private_key_file) self.assertIn('client_secret', signed_params) signature = signed_params['client_secret'] message = ''.join([scope, timestamp, client_id, state]) def write_to_temp_file(content): """ Writes content to temp file and returns file name :param content: binary content :return: file path """ temp_file = tempfile.NamedTemporaryFile(mode='wb', delete=False) temp_file.write(content) temp_file.close() return temp_file.name message_path = write_to_temp_file(message.encode()) try: encoded_signature = base64.urlsafe_b64decode(signature) except Exception as e: self.fail("client_id is not urlsafe base64 encoded string! (Exc: %s)" % e) signature_path = write_to_temp_file(encoded_signature) verify_cmd_tpl = "openssl smime -verify -inform DER -in {sig} -content {cnt} -noverify -certfile {cert_file}" verify_cmd = verify_cmd_tpl.format( sig=signature_path, cnt=message_path, cert_file=TEST_SETTINGS.certificate_file ) print(verify_cmd) res = os.system(verify_cmd) self.assertEqual(res, 0, "Signature verification failed!")
def complete_authorization(self, code, state, validate_token=True, redirect_uri=None): """ Exchanges received code and state to access token, validates token (optionally), extracts ESIA user id from token and returns ESIAInformationConnector instance. :type code: str :type state: str :param boolean validate_token: perform token validation :param str or None redirect_uri: uri, where browser will be redirected after authorization. :rtype: EsiaInformationConnector :raises IncorrectJsonError: if response contains invalid json body :raises HttpError: if response status code is not 2XX :raises IncorrectMarkerError: if validate_token set to True and received token cannot be validated """ params = { 'client_id': self.settings.esia_client_id, 'code': code, 'grant_type': 'authorization_code', 'redirect_uri': redirect_uri or self.settings.redirect_uri, 'timestamp': get_timestamp(), 'token_type': 'Bearer', 'scope': self.settings.esia_scope, 'state': state, } params = sign_params(params, certificate_file=self.settings.certificate_file, private_key_file=self.settings.private_key_file) url = '{base_url}{token_url}'.format( base_url=self.settings.esia_service_url, token_url=self._TOKEN_EXCHANGE_URL) response_json = make_request(url=url, method='POST', data=params) id_token = response_json['id_token'] if validate_token: payload = self._validate_token(id_token) else: payload = self._parse_token(id_token) return EsiaInformationConnector( access_token=response_json['access_token'], oid=self._get_user_id(payload), settings=self.settings)
def complete_authorization(self, code, state, validate_token=True, redirect_uri=None): """ Exchanges received code and state to access token, validates token (optionally), extracts ESIA user id from token and returns ESIAInformationConnector instance. :type code: str :type state: str :param boolean validate_token: perform token validation :param str or None redirect_uri: uri, where browser will be redirected after authorization. :rtype: EsiaInformationConnector :raises IncorrectJsonError: if response contains invalid json body :raises HttpError: if response status code is not 2XX :raises IncorrectMarkerError: if validate_token set to True and received token cannot be validated """ params = { 'client_id': self.settings.esia_client_id, 'code': code, 'grant_type': 'authorization_code', 'redirect_uri': redirect_uri or self.settings.redirect_uri, 'timestamp': get_timestamp(), 'token_type': 'Bearer', 'scope': self.settings.esia_scope, 'state': state, } params = sign_params(params, certificate_file=self.settings.certificate_file, private_key_file=self.settings.private_key_file) url = '{base_url}{token_url}'.format(base_url=self.settings.esia_service_url, token_url=self._TOKEN_EXCHANGE_URL) response_json = make_request(url=url, method='POST', data=params) id_token = response_json['id_token'] if validate_token: payload = self._validate_token(id_token) else: payload = self._parse_token(id_token) return EsiaInformationConnector(access_token=response_json['access_token'], oid=self._get_user_id(payload), settings=self.settings)