Skip to content

Commit 4f97223

Browse files
Merge pull request #282 from sgomez/fix-orcid-auth
Fix orcid authentication and access to API issues
2 parents 803bf24 + 9d5f2fe commit 4f97223

File tree

2 files changed

+218
-3
lines changed

2 files changed

+218
-3
lines changed

src/satosa/backends/orcid.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,13 @@ def user_information(self, access_token, orcid, name):
9696
url = urljoin(base_url, '{}/person'.format(orcid))
9797
headers = {
9898
'Accept': 'application/orcid+json',
99-
'Authorization type': 'Bearer',
100-
'Access token': access_token,
99+
'Authorization': "Bearer {}".format(access_token)
101100
}
102101
r = requests.get(url, headers=headers)
103102
r = r.json()
104103
emails, addresses = r['emails']['email'], r['addresses']['address']
105104
ret = dict(
106-
address=', '.join([e['address'] for e in addresses]),
105+
address=', '.join([e['country']['value'] for e in addresses]),
107106
displayname=name,
108107
edupersontargetedid=orcid, orcid=orcid,
109108
mail=' '.join([e['email'] for e in emails]),

tests/satosa/backends/test_orcid.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import json
2+
import pytest
3+
import responses
4+
5+
from satosa.backends.orcid import OrcidBackend
6+
from satosa.context import Context
7+
from satosa.internal import InternalData
8+
from satosa.response import Response
9+
from unittest.mock import Mock
10+
from urllib.parse import urljoin, urlparse, parse_qsl
11+
12+
ORCID_PERSON_ID = "0000-0000-0000-0000"
13+
ORCID_PERSON_GIVEN_NAME = "orcid_given_name"
14+
ORCID_PERSON_FAMILY_NAME = "orcid_family_name"
15+
ORCID_PERSON_NAME = "{} {}".format(ORCID_PERSON_GIVEN_NAME, ORCID_PERSON_FAMILY_NAME)
16+
ORCID_PERSON_EMAIL = "orcid_email"
17+
ORCID_PERSON_COUNTRY = "XX"
18+
19+
20+
class TestOrcidBackend(object):
21+
@pytest.fixture(autouse=True)
22+
def create_backend(self, internal_attributes, backend_config):
23+
self.orcid_backend = OrcidBackend(
24+
Mock(),
25+
internal_attributes,
26+
backend_config,
27+
backend_config["base_url"],
28+
"orcid"
29+
)
30+
31+
@pytest.fixture
32+
def backend_config(self):
33+
return {
34+
"authz_page": 'orcid/auth/callback',
35+
"base_url": "https://client.example.com",
36+
"client_config": {"client_id": "orcid_client_id"},
37+
"client_secret": "orcid_secret",
38+
"scope": ["/authenticate"],
39+
"response_type": "code",
40+
"server_info": {
41+
"authorization_endpoint": "https://orcid.org/oauth/authorize",
42+
"token_endpoint": "https://pub.orcid.org/oauth/token",
43+
"user_info": "https://pub.orcid.org/v2.0/"
44+
}
45+
}
46+
47+
@pytest.fixture
48+
def internal_attributes(self):
49+
return {
50+
"attributes": {
51+
"address": {"orcid": ["address"]},
52+
"displayname": {"orcid": ["name"]},
53+
"edupersontargetedid": {"orcid": ["orcid"]},
54+
"givenname": {"orcid": ["givenname"]},
55+
"mail": {"orcid": ["mail"]},
56+
"name": {"orcid": ["name"]},
57+
"surname": {"orcid": ["surname"]},
58+
}
59+
}
60+
61+
@pytest.fixture
62+
def userinfo(self):
63+
return {
64+
"name": {
65+
"given-names": {"value": ORCID_PERSON_GIVEN_NAME},
66+
"family-name": {"value": ORCID_PERSON_FAMILY_NAME},
67+
},
68+
"emails": {
69+
"email": [
70+
{
71+
"email": ORCID_PERSON_EMAIL,
72+
"verified": True,
73+
"primary": True
74+
}
75+
]
76+
},
77+
"addresses": {
78+
"address": [
79+
{"country": {"value": ORCID_PERSON_COUNTRY}}
80+
]
81+
}
82+
}
83+
84+
@pytest.fixture
85+
def userinfo_private(self):
86+
return {
87+
"name": {
88+
"given-names": {"value": ORCID_PERSON_GIVEN_NAME},
89+
"family-name": {"value": ORCID_PERSON_FAMILY_NAME},
90+
},
91+
"emails": {
92+
"email": [
93+
]
94+
},
95+
"addresses": {
96+
"address": [
97+
]
98+
}
99+
}
100+
101+
def assert_expected_attributes(self, user_claims, actual_attributes):
102+
print(user_claims)
103+
print(actual_attributes)
104+
105+
expected_attributes = {
106+
"address": [ORCID_PERSON_COUNTRY],
107+
"displayname": [ORCID_PERSON_NAME],
108+
"edupersontargetedid": [ORCID_PERSON_ID],
109+
"givenname": [ORCID_PERSON_GIVEN_NAME],
110+
"mail": [ORCID_PERSON_EMAIL],
111+
"name": [ORCID_PERSON_NAME],
112+
"surname": [ORCID_PERSON_FAMILY_NAME],
113+
}
114+
115+
assert actual_attributes == expected_attributes
116+
117+
def setup_token_endpoint(self, token_endpoint_url):
118+
token_response = {
119+
"access_token": "orcid_access_token",
120+
"token_type": "bearer",
121+
"expires_in": 9999999999999,
122+
"name": ORCID_PERSON_NAME,
123+
"orcid": ORCID_PERSON_ID
124+
}
125+
126+
responses.add(
127+
responses.POST,
128+
token_endpoint_url,
129+
body=json.dumps(token_response),
130+
status=200,
131+
content_type="application/json"
132+
)
133+
134+
def setup_userinfo_endpoint(self, userinfo_endpoint_url, userinfo):
135+
responses.add(
136+
responses.GET,
137+
urljoin(userinfo_endpoint_url, '{}/person'.format(ORCID_PERSON_ID)),
138+
body=json.dumps(userinfo),
139+
status=200,
140+
content_type="application/json"
141+
)
142+
143+
@pytest.fixture
144+
def incoming_authn_response(self, context, backend_config):
145+
context.path = backend_config["authz_page"]
146+
context.request = {
147+
"code": "the_orcid_code",
148+
}
149+
150+
return context
151+
152+
def test_start_auth(self, context, backend_config):
153+
auth_response = self.orcid_backend.start_auth(context, None)
154+
assert isinstance(auth_response, Response)
155+
156+
login_url = auth_response.message
157+
parsed = urlparse(login_url)
158+
assert login_url.startswith(backend_config["server_info"]["authorization_endpoint"])
159+
auth_params = dict(parse_qsl(parsed.query))
160+
assert auth_params["scope"] == " ".join(backend_config["scope"])
161+
assert auth_params["response_type"] == backend_config["response_type"]
162+
assert auth_params["client_id"] == backend_config["client_config"]["client_id"]
163+
assert auth_params["redirect_uri"] == "{}/{}".format(
164+
backend_config["base_url"],
165+
backend_config["authz_page"]
166+
)
167+
168+
@responses.activate
169+
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)
172+
173+
self.orcid_backend._authn_response(incoming_authn_response)
174+
175+
args = self.orcid_backend.auth_callback_func.call_args[0]
176+
assert isinstance(args[0], Context)
177+
assert isinstance(args[1], InternalData)
178+
179+
self.assert_expected_attributes(userinfo, args[1].attributes)
180+
181+
@responses.activate
182+
def test_user_information(self, context, backend_config, userinfo):
183+
self.setup_userinfo_endpoint(
184+
backend_config["server_info"]["user_info"],
185+
userinfo
186+
)
187+
188+
user_attributes = self.orcid_backend.user_information(
189+
"orcid_access_token",
190+
ORCID_PERSON_ID,
191+
ORCID_PERSON_NAME
192+
)
193+
194+
assert user_attributes["address"] == ORCID_PERSON_COUNTRY
195+
assert user_attributes["displayname"] == ORCID_PERSON_NAME
196+
assert user_attributes["edupersontargetedid"] == ORCID_PERSON_ID
197+
assert user_attributes["orcid"] == ORCID_PERSON_ID
198+
assert user_attributes["mail"] == ORCID_PERSON_EMAIL
199+
assert user_attributes["givenname"] == ORCID_PERSON_GIVEN_NAME
200+
assert user_attributes["surname"] == ORCID_PERSON_FAMILY_NAME
201+
202+
@responses.activate
203+
def test_user_information_private(self, context, backend_config, userinfo_private):
204+
self.setup_userinfo_endpoint(
205+
backend_config["server_info"]["user_info"],
206+
userinfo_private
207+
)
208+
209+
user_attributes = self.orcid_backend.user_information(
210+
"orcid_access_token",
211+
ORCID_PERSON_ID,
212+
ORCID_PERSON_NAME
213+
)
214+
215+
assert user_attributes["address"] == ""
216+
assert user_attributes["mail"] == ""

0 commit comments

Comments
 (0)