0002-add-rp-remote-idp-authentication-16842.patch
fargo/oauth2/rest_authentication.py | ||
---|---|---|
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
import base64 |
18 |
import urlparse |
|
19 |
import logging |
|
18 | 20 | |
21 |
import requests |
|
22 | ||
23 |
from django.conf import settings |
|
19 | 24 |
from django.utils.translation import ugettext_lazy as _ |
20 | 25 | |
21 | 26 |
from rest_framework.authentication import BaseAuthentication |
... | ... | |
79 | 84 | |
80 | 85 |
return self.authenticate_credentials(client_id, client_secret) |
81 | 86 | |
87 |
def authenticate_through_idp(self, client_id, client_secret): |
|
88 |
'''Check client_id and client_secret with configured IdP, and verify it is an OIDC |
|
89 |
client. |
|
90 |
''' |
|
91 |
logger = logging.getLogger(__name__) |
|
92 | ||
93 |
authentic_idp = getattr(settings, 'FARGO_IDP_URL', None) |
|
94 |
if not authentic_idp: |
|
95 |
logger.warning(u'idp check-password not configured') |
|
96 |
return False, '' |
|
97 | ||
98 |
url = urlparse.urljoin(authentic_idp, 'api/check-password/') |
|
99 |
try: |
|
100 |
response = requests.post(url, json={ |
|
101 |
'username': client_id, |
|
102 |
'password': client_secret}, auth=(client_id, client_secret), verify=False) |
|
103 |
response.raise_for_status() |
|
104 |
except requests.RequestException as e: |
|
105 |
logger.warning(u'idp check-password API failed: %s', e) |
|
106 |
return False, 'idp is down' |
|
107 |
try: |
|
108 |
response = response.json() |
|
109 |
except ValueError as e: |
|
110 |
logger.warning(u'idp check-password API failed: %s, %r', e, response.content) |
|
111 |
return False, 'idp is down' |
|
112 | ||
113 |
if response.get('result') == 0: |
|
114 |
logger.warning(u'idp check-password API failed') |
|
115 |
return False, response.get('errors', [''])[0] |
|
116 | ||
117 |
return True, None |
|
118 | ||
82 | 119 |
def authenticate_credentials(self, client_id, client_secret): |
83 | 120 |
try: |
84 | 121 |
client = OAuth2Client.objects.get( |
85 | 122 |
client_id=client_id, client_secret=client_secret) |
86 | 123 |
except OAuth2Client.DoesNotExist: |
87 |
raise AuthenticationFailed(_('Invalid client_id/client_secret.')) |
|
124 |
success, error = self.authenticate_through_idp(client_id, client_secret) |
|
125 |
if not success: |
|
126 |
raise AuthenticationFailed(error or _('Invalid client_id/client_secret.')) |
|
127 |
client = OAuth2Client.objects.get(client_id=client_id) |
|
128 |
client.client_secret = client_secret |
|
129 |
client.save() |
|
88 | 130 | |
89 | 131 |
user = OAuth2User(client) |
90 | 132 |
user.authenticated = True |
tests/test_oauth2.py | ||
---|---|---|
1 |
import json |
|
1 | 2 |
import pytest |
2 | 3 |
from urllib import quote |
3 | 4 |
import urlparse |
5 |
import mock |
|
4 | 6 | |
5 | 7 |
from django.core.files.base import ContentFile |
6 | 8 |
from django.core.urlresolvers import reverse |
... | ... | |
14 | 16 |
pytestmark = pytest.mark.django_db |
15 | 17 | |
16 | 18 | |
19 |
class FakedResponse(mock.Mock): |
|
20 | ||
21 |
def json(self): |
|
22 |
return json.loads(self.content) |
|
23 | ||
24 | ||
17 | 25 |
@pytest.fixture |
18 | 26 |
def oauth2_client(): |
19 | 27 |
return OAuth2Client.objects.create( |
... | ... | |
160 | 168 |
url += '?%s' % urlencode({'redirect_uri': 'https://example.com'}) |
161 | 169 |
resp = app.get(url) |
162 | 170 |
assert 'This document is already in your portfolio' in resp.content |
171 | ||
172 | ||
173 |
@mock.patch('fargo.oauth2.rest_authentication.requests.post') |
|
174 |
def test_idp_authentication(mocked_post, settings, app, oauth2_client, john_doe, user_doc): |
|
175 |
login(app, user=john_doe) |
|
176 |
url = reverse('oauth2-authorize') |
|
177 |
params = { |
|
178 |
'client_id': oauth2_client.client_id, |
|
179 |
'client_secret': 'fake', |
|
180 |
'response_type': 'code', |
|
181 |
'state': 'achipeachope', |
|
182 |
'redirect': 'https://example.com/' |
|
183 |
} |
|
184 |
params['redirect_uri'] = 'https://example.com' |
|
185 |
resp = app.get(url, params=params) |
|
186 |
resp.forms[0]['document'].select('1') |
|
187 |
resp = resp.forms[0].submit() |
|
188 |
auth = OAuth2Authorize.objects.filter(user_document__user=john_doe)[0] |
|
189 |
params.pop('response_type') |
|
190 |
params.pop('state') |
|
191 |
params['grant_type'] = 'authorization_code' |
|
192 |
params['code'] = auth.code |
|
193 | ||
194 |
url = reverse('oauth2-get-token') |
|
195 |
# when remote remote idp not set |
|
196 |
resp = app.post(url, params=params, status=401) |
|
197 |
resp.json['detail'] == 'Invalid client_id/client_secret.' |
|
198 |
# when remote idp fails to authenticate rp |
|
199 |
settings.FARGO_IDP_URL = 'https://idp.example.org' |
|
200 |
response = { |
|
201 |
"result": 0, "errors": ["Invalid username/password."] |
|
202 |
} |
|
203 |
mocked_post.return_value = FakedResponse(content=json.dumps(response)) |
|
204 |
resp = app.post(url, params=params, status=401) |
|
205 |
resp.json['detail'] == 'Invalid client_id/client_secret.' |
|
206 |
# when remote idp authenticates rp |
|
207 |
response = {"result": 1, "errors": []} |
|
208 |
mocked_post.return_value = FakedResponse(content=json.dumps(response)) |
|
209 |
resp = app.post(url, params=params, status=200) |
|
210 |
assert resp.json['access_token'] == auth.access_token |
|
211 |
url = reverse('oauth2-get-document') |
|
212 |
app.authorization = ('Bearer', str(auth.access_token)) |
|
213 |
resp = app.get(url, status=200) |
|
163 |
- |