#366 Add support of Out Of Band authentication
Closed 2 years ago by ngompa. Opened 2 years ago by abompard.
abompard/ipsilon oob  into  master

@@ -8,7 +8,8 @@ 

                                             TokenInfo,

                                             UserInfo)

  from ipsilon.providers.openidc.provider import (get_url_hostpart,

-                                                 Registration)

+                                                 Registration,

+                                                 OOB_URL)

  from ipsilon.util.user import UserSession

  

  from jwcrypto.jwt import JWT
@@ -65,7 +66,9 @@ 

          url = request['redirect_uri']

          response_mode = request.get('response_mode', None)

          response_type = request.get('response_type', [])

-         if 'none' in response_type:

+         if url == OOB_URL:

+             response_mode = "oob"

+         elif 'none' in response_type:

              response_mode = 'none'

              self.debug('none response_type, using none response_mode')

          elif 'id_token' in response_type or 'token' in response_type:
@@ -112,6 +115,12 @@ 

                  "response_info": contents

              }

              return self._template(URLROOT + '/form_response.html', **context)

+         elif response_mode == "oob":

+             context = {

+                 "title": urlencode(contents),

+                 "response_info": contents

+             }

+             return self._template(URLROOT + '/oob_response.html', **context)

          else:

              raise InvalidRequest('Invalid response_mode requested')

  
@@ -745,7 +754,7 @@ 

              'response_types_supported': ['code', 'id_token', 'token',

                                           'token id_token'],

              'response_modes_supported': ['query', 'fragment', 'form_post',

-                                          'none'],

+                                          'oob', 'none'],

              'grant_types_supported': ['authorization_code', 'implicit',

                                        'refresh_token'],

              'acr_values_supported': ['0'],

@@ -13,7 +13,12 @@ 

  from six.moves.urllib.parse import urlparse

  

  

+ OOB_URL = "urn:ietf:wg:oauth:2.0:oob"

+ 

+ 

  def get_url_hostpart(url):

+     if url == OOB_URL:

+         return url

      try:

          o = urlparse(url)

          return o.hostname

file modified
+3
@@ -3499,3 +3499,6 @@ 

    height: 18px;

    padding-left: 1em;

  }

+ .ipsilon-oob pre {

+   font-size: 130%;

+ }

@@ -0,0 +1,14 @@ 

+ {% extends "master.html" %}

+ {% block main %}

+ <div class="col-sm-12">

+ <p><strong>You are authenticated!</strong> Please copy and paste the following code in the application:</p>

+ </div>

+ 

+ <div class="col-sm-12 ipsilon-oob">

+   <pre>{{ response_info|urlencode }}</pre>

+ </div>

+ 

+ <div class="col-sm-12">

+ <p>You can close this browser window afterwards.</p>

+ </div>

+ {% endblock %}

file modified
+8
@@ -267,6 +267,14 @@ 

  

          return ['post', url, {'data': params}]

  

+     def get_openidc_oob(self, page):

+         if not isinstance(page, PageTree):

+             raise TypeError("Expected PageTree object")

+         result = page.first_value(

+             '//div[contains(@class, "ipsilon-oob")]/pre'

+         )

+         return result.text

+ 

      def fetch_page(self, idp, target_url, follow_redirect=True, krb=False,

                     require_consent=None, return_prefix=None, post_forms=True):

          """

file modified
+51
@@ -497,3 +497,54 @@ 

          page = sess3.fetch_page(idpname, 'https://127.0.0.11:45081/sp/')

          check_text_results(page.text,

                             'OpenID Connect Provider error: access_denied')

+ 

+     with TC.case('Set IdP authz stack to back to allow'):

+         sess.disable_plugin(idpname, 'authz', 'deny')

+         sess.enable_plugin(idpname, 'authz', 'allow')

+ 

+     sess4 = HttpSessions()

+     sess4.add_server(idpname, 'https://127.0.0.10:45080', user, 'ipsilon')

+     sess4.add_server(sp1name, 'https://127.0.0.11:45081')

+ 

+     with TC.case('Registering test client with OOB'):

+         client_info = {

+             'redirect_uris': ['urn:ietf:wg:oauth:2.0:oob'],

+             'response_types': ['code'],

+             'grant_types': ['authorization_code'],

+             'application_type': 'native',

+             'client_name': 'Test suite client',

+             'client_uri': 'https://invalid/',

+             'token_endpoint_auth_method': 'none'

+         }

+         r = requests.post('https://127.0.0.10:45080/idp1/openidc/Registration',

+                           json=client_info)

+         r.raise_for_status()

+         reg_resp_oob = r.json()

+ 

+     with TC.case('Access first SP protected area with OOB'):

+         page = sess.fetch_page(idpname,

+                                'https://127.0.0.10:45080/idp1/openidc/'

+                                'Authorization?scope=openid&response_type=code&'

+                                'redirect_uri=urn:ietf:wg:oauth:2.0:oob&'

+                                'client_id=' + reg_resp_oob['client_id'])

+         code = sess.get_openidc_oob(page)

+         title_value = page.first_value('/html/head/title').text

+         if title_value != code:

+             raise Exception(

+                 "The title of the page must contain the code as well"

+             )

+         code = code.replace('code=', '')

+         # Now check that we can get a token

+         token_resp = requests.post(

+             'https://127.0.0.10:45080/idp1/openidc/Token',

+             data={'client_id': reg_resp_oob['client_id'],

+                   'grant_type': 'authorization_code',

+                   'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',

+                   'code': code})

+         if token_resp.status_code != 200:

+             raise Exception('Unable to get token from code')

+         anon_token = token_resp.json()

+         if not anon_token.get('token_type') == 'Bearer':

+             raise Exception('Invalid token type returned')

+         if 'access_token' not in anon_token:

+             raise Exception('Did not get access token')

file modified
+3
@@ -3499,3 +3499,6 @@ 

    height: 18px;

    padding-left: 1em;

  }

+ .ipsilon-oob pre {

+   font-size: 130%;

+ }

When the redirect_uri has a special value for OOB authentication, display a page prompting the user to copy and paste the code back to the application. Use the code as the page's title as well.

This is useful when it's not practical for the client to run an HTTP server and use a localhost redirect uri, such as when it's executing on a remote machine.

References:
- https://github.com/googleapis/google-api-python-client/blob/main/docs/oauth-installed.md#urnietfwgoauth20oob
- https://developers.google.com/identity/protocols/oauth2/native-app#step-2:-send-a-request-to-googles-oauth-2.0-server

Pull-Request has been closed by ngompa

2 years ago