Skip to content

Commit 10b9122

Browse files
Merge pull request #284 from sgomez/orcid-state
Add state parameter to ORCID authorization request
2 parents 4f97223 + 8faf893 commit 10b9122

File tree

2 files changed

+42
-39
lines changed

2 files changed

+42
-39
lines changed

src/satosa/backends/orcid.py

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from satosa.backends.oauth import _OAuthBackend
1414
from satosa.internal import InternalData
1515
from satosa.internal import AuthenticationInformation
16-
from satosa.response import Redirect
16+
from satosa.util import rndstr
1717

1818
logger = logging.getLogger(__name__)
1919

@@ -45,50 +45,41 @@ def __init__(self, outgoing, internal_attributes, config, base_url, name):
4545
outgoing, internal_attributes, config, base_url, name, 'orcid',
4646
'orcid')
4747

48-
def start_auth(self, context, internal_request, get_state=stateID):
49-
"""
50-
:param get_state: Generates a state to be used in authentication call
51-
52-
:type get_state: Callable[[str, bytes], str]
53-
:type context: satosa.context.Context
54-
:type internal_request: satosa.internal.InternalData
55-
:rtype satosa.response.Redirect
56-
"""
57-
request_args = dict(
58-
client_id=self.config['client_config']['client_id'],
59-
redirect_uri=self.redirect_url,
60-
scope=' '.join(self.config['scope']), )
61-
cis = self.consumer.construct_AuthorizationRequest(
62-
request_args=request_args)
63-
return Redirect(cis.request(self.consumer.authorization_endpoint))
48+
def get_request_args(self, get_state=stateID):
49+
oauth_state = get_state(self.config["base_url"], rndstr().encode())
50+
request_args = {
51+
"client_id": self.config['client_config']['client_id'],
52+
"redirect_uri": self.redirect_url,
53+
"scope": ' '.join(self.config['scope']),
54+
"state": oauth_state,
55+
}
56+
return request_args
6457

6558
def auth_info(self, requrest):
6659
return AuthenticationInformation(
6760
UNSPECIFIED, None,
6861
self.config['server_info']['authorization_endpoint'])
6962

7063
def _authn_response(self, context):
64+
state_data = context.state[self.name]
7165
aresp = self.consumer.parse_response(
7266
AuthorizationResponse, info=json.dumps(context.request))
73-
url = self.config['server_info']['token_endpoint']
74-
data = dict(
75-
grant_type='authorization_code',
76-
code=aresp['code'],
77-
redirect_uri=self.redirect_url,
78-
client_id=self.config['client_config']['client_id'],
79-
client_secret=self.config['client_secret'], )
80-
headers = {'Accept': 'application/json'}
67+
self._verify_state(aresp, state_data, context.state)
68+
69+
rargs = {"code": aresp["code"], "redirect_uri": self.redirect_url,
70+
"state": state_data["state"]}
71+
72+
atresp = self.consumer.do_access_token_request(
73+
request_args=rargs, state=aresp['state'])
8174

82-
r = requests.post(url, data=data, headers=headers)
83-
response = r.json()
84-
token = response['access_token']
85-
orcid, name = response['orcid'], response['name']
86-
user_info = self.user_information(token, orcid, name)
87-
auth_info = self.auth_info(context.request)
88-
internal_response = InternalData(auth_info=auth_info)
75+
user_info = self.user_information(
76+
atresp['access_token'], atresp['orcid'], atresp['name'])
77+
internal_response = InternalData(
78+
auth_info=self.auth_info(context.request))
8979
internal_response.attributes = self.converter.to_internal(
9080
self.external_type, user_info)
91-
internal_response.subject_id = orcid
81+
internal_response.subject_id = user_info[self.user_id_attr]
82+
del context.state[self.name]
9283
return self.auth_callback_func(context, internal_response)
9384

9485
def user_information(self, access_token, orcid, name):

tests/satosa/backends/test_orcid.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212
ORCID_PERSON_ID = "0000-0000-0000-0000"
1313
ORCID_PERSON_GIVEN_NAME = "orcid_given_name"
1414
ORCID_PERSON_FAMILY_NAME = "orcid_family_name"
15-
ORCID_PERSON_NAME = "{} {}".format(ORCID_PERSON_GIVEN_NAME, ORCID_PERSON_FAMILY_NAME)
15+
ORCID_PERSON_NAME = "{} {}".format(
16+
ORCID_PERSON_GIVEN_NAME, ORCID_PERSON_FAMILY_NAME)
1617
ORCID_PERSON_EMAIL = "orcid_email"
1718
ORCID_PERSON_COUNTRY = "XX"
1819

20+
mock_get_state = Mock(return_value="abcdef")
21+
1922

2023
class TestOrcidBackend(object):
2124
@pytest.fixture(autouse=True)
@@ -134,7 +137,8 @@ def setup_token_endpoint(self, token_endpoint_url):
134137
def setup_userinfo_endpoint(self, userinfo_endpoint_url, userinfo):
135138
responses.add(
136139
responses.GET,
137-
urljoin(userinfo_endpoint_url, '{}/person'.format(ORCID_PERSON_ID)),
140+
urljoin(userinfo_endpoint_url,
141+
'{}/person'.format(ORCID_PERSON_ID)),
138142
body=json.dumps(userinfo),
139143
status=200,
140144
content_type="application/json"
@@ -143,19 +147,24 @@ def setup_userinfo_endpoint(self, userinfo_endpoint_url, userinfo):
143147
@pytest.fixture
144148
def incoming_authn_response(self, context, backend_config):
145149
context.path = backend_config["authz_page"]
150+
state_data = dict(state=mock_get_state.return_value)
151+
context.state[self.orcid_backend.name] = state_data
146152
context.request = {
147153
"code": "the_orcid_code",
154+
"state": mock_get_state.return_value
148155
}
149156

150157
return context
151158

152159
def test_start_auth(self, context, backend_config):
153-
auth_response = self.orcid_backend.start_auth(context, None)
160+
auth_response = self.orcid_backend.start_auth(
161+
context, None, mock_get_state)
154162
assert isinstance(auth_response, Response)
155163

156164
login_url = auth_response.message
157165
parsed = urlparse(login_url)
158-
assert login_url.startswith(backend_config["server_info"]["authorization_endpoint"])
166+
assert login_url.startswith(
167+
backend_config["server_info"]["authorization_endpoint"])
159168
auth_params = dict(parse_qsl(parsed.query))
160169
assert auth_params["scope"] == " ".join(backend_config["scope"])
161170
assert auth_params["response_type"] == backend_config["response_type"]
@@ -164,11 +173,14 @@ def test_start_auth(self, context, backend_config):
164173
backend_config["base_url"],
165174
backend_config["authz_page"]
166175
)
176+
assert auth_params["state"] == mock_get_state.return_value
167177

168178
@responses.activate
169179
def test_authn_response(self, backend_config, userinfo, incoming_authn_response):
170-
self.setup_token_endpoint(backend_config["server_info"]["token_endpoint"])
171-
self.setup_userinfo_endpoint(backend_config["server_info"]["user_info"], userinfo)
180+
self.setup_token_endpoint(
181+
backend_config["server_info"]["token_endpoint"])
182+
self.setup_userinfo_endpoint(
183+
backend_config["server_info"]["user_info"], userinfo)
172184

173185
self.orcid_backend._authn_response(incoming_authn_response)
174186

0 commit comments

Comments
 (0)