Exemplo n.º 1
0
def callback():
    """Retrieving an access token.
    After you've redirected from our provider to your callback URL,
    you'll have access to the auth code in the redirect URL, which
    we'll be using to get an access token.
    """

    client = WebApplicationClient(client_id=CLIENT_ID)
    # Parse the response URI after the callback, with the same state we initially sent
    client.parse_request_uri_response(request.url,
                                      state=session["oauth_state"])
    # Now we've access to the auth code
    code = client.code

    # Prepare request body to get the access token
    body = client.prepare_request_body(
        code=code,
        redirect_uri=REDIRECT_URI,
        include_client_id=False,
        scope=scope,
    )

    # Basic HTTP auth by providing your client credentials
    auth = requests.auth.HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET)

    # Making a post request to the TOKEN_URL endpoint
    r = requests.post(TOKEN_URL, data=dict(urldecode(body)), auth=auth)

    # Parse the response to get the token and store it in session
    token = client.parse_request_body_response(r.text, scope=scope)
    session["access_token"] = token

    return redirect("/home")
Exemplo n.º 2
0
def test_oauth2(app, user, oauth2_client):
    client_id, client_secret = oauth2_client
    client = WebApplicationClient(client_id)
    host = "https://localhost"
    state = "randomly_text"

    with app.test_client() as provider:
        # login forcefully.
        with provider.session_transaction() as sess:
            sess["user_id"] = user
            sess["_fresh"] = True
        uri = client.prepare_request_uri(host + "/oauth2/authorize", redirect_uri=redirect_uri, state=state)
        uri = uri[len(host) :]
        # step 1: redirect to provider
        response = provider.get(uri, follow_redirects=True)
        assert response.status_code == 200
        # step 2: redirect to client
        response = provider.post(uri, data={"scope": "user", "confirm": "yes"})
        assert response.location.startswith(redirect_uri)
        data = client.parse_request_uri_response(response.location, state=state)
        assert "code" in data
        # step 3: get the token
        body = client.prepare_request_body(code=data["code"], redirect_uri=redirect_uri)
        response = provider.post("/oauth2/token", content_type="application/x-www-form-urlencoded", data=body)
        assert response.status_code == 200
        data = client.parse_request_body_response(response.data)
        assert "access_token" in data
        assert data["token_type"] == "Bearer"
        assert data["scope"] == ["user"]
        # step 4: using token
        pass
Exemplo n.º 3
0
class TestOauthlib(ApprovalTestCase):
    
    def setUp(self):
        super(TestOauthlib, self).setUp()
        
        self.libclient = WebApplicationClient(self.oauth_client.id)
        
        self.authorization_code.response_type = "code"
        self.authorization_code.save()
    
    def test_flow(self):
        self.client.login(username="******", password="******")
        
        request_uri = self.libclient.prepare_request_uri(
            "https://localhost" + reverse("oauth2_authorize"),
            redirect_uri=self.redirect_uri.url,
            scope=["test", ],
            state="test_state",
        )
        
        response = self.client.get(request_uri[17:])
        
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "doac/authorize.html")
        
        approval_url = reverse("oauth2_approval") + "?code=" + self.authorization_code.token
        
        response = self.client.post(approval_url, {
            "code": self.authorization_code.token,
            "code_state": "test_state",
            "approve_access": None,
        })
        
        response_uri = response.get("location", None)
        
        if not response_uri:
            response_uri = response.META["HTTP_LOCATION"]
        
        response_uri = response_uri.replace("http://", "https://")
        
        data = self.libclient.parse_request_uri_response(response_uri, state="test_state")
        
        authorization_token = data["code"]
        
        request_body = self.libclient.prepare_request_body(
            client_secret=self.oauth_client.secret,
            code=authorization_token,
        )
        
        post_dict = {}
        
        for pair in request_body.split('&'):
            key, val = pair.split('=')
            
            post_dict[key] = val
        
        response = self.client.post(reverse("oauth2_token"), post_dict)
        
        data = self.libclient.parse_request_body_response(response.content)
class OAuthAuthorizationCodeGrantRequest:
    def __init__(self, redirect_uri: str, settings: OAuthSettings):
        from aiohttp import ClientSession
        from oauthlib.oauth2 import WebApplicationClient

        self.settings = settings
        self.future = get_event_loop().create_future()
        self.state = uuid.uuid4().hex
        self.client = WebApplicationClient(settings.client_id)
        self.redirect_uri = redirect_uri
        self.session = ClientSession()

    async def start(self) -> None:
        uri = self.client.prepare_request_uri(
            self.settings.token_uri, redirect_uri=self.redirect_uri, state=self.state)
        body = self.client.prepare_request_body()

        async with self.session.post(uri, data=body) as response:
            print(response.status)
            print(await response.text())

    async def handle_redirect(self, uri: str, code: str, state: str) -> OAuthSettings:
        self.client.parse_request_uri_response(uri, state)

        kwargs = {}
        if self.settings.client_secret:
            kwargs['client_secret'] = self.settings.client_secret

        uri = self.settings.token_uri
        body = self.client.prepare_request_body(
            code=code, redirect_uri=self.redirect_uri, **kwargs)

        # fetch token

        self.client.parse_request_body_response(response_body)

        new_settings = OAuthSettings(

        )

        self.future.set_result(new_settings)
        return new_settings

    async def close(self):
        await self.session.close()
Exemplo n.º 5
0
    def test_request_body(self):
        client = WebApplicationClient(self.client_id, code=self.code)

        # Basic, no extra arguments
        body = client.prepare_request_body(body=self.body)
        self.assertFormBodyEqual(body, self.body_code)

        rclient = WebApplicationClient(self.client_id)
        body = rclient.prepare_request_body(code=self.code, body=self.body)
        self.assertFormBodyEqual(body, self.body_code)

        # With redirection uri
        body = client.prepare_request_body(body=self.body, redirect_uri=self.redirect_uri)
        self.assertFormBodyEqual(body, self.body_redirect)

        # With extra parameters
        body = client.prepare_request_body(body=self.body, **self.kwargs)
        self.assertFormBodyEqual(body, self.body_kwargs)
Exemplo n.º 6
0
def verify_token(request):
    dataporten_oauth_client = WebApplicationClient(settings.DATAPORTEN_ID)

    code = request.GET.get('code')

    redirect_uri = settings.DATAPORTEN_REDIRECT_URI

    token_request_body = dataporten_oauth_client.prepare_request_body(
        code=code,
        redirect_uri=redirect_uri,
        client_secret=settings.DATAPORTEN_SECRET)

    token_request_response = requests.post(
        settings.DATAPORTEN_OAUTH_TOKEN_URL,
        data=token_request_body,
        headers={
            'content-type': 'application/x-www-form-urlencoded',
            'authorization': 'Basic {}'.format(settings.DATAPORTEN_SECRET),
        })

    if token_request_response.status_code != 200:
        raise Exception('invalid code')

    response_json = token_request_response.json()

    session = requests.Session()
    session.headers.update(
        {'authorization': 'bearer {}'.format(response_json['access_token'])})
    user_info = session.get(settings.DATAPORTEN_USER_INFO_URL).json()['user']
    user_mail = user_info['email']
    user, created = User.objects.get_or_create(email=user_mail,
                                               username=user_mail)
    ExpiringToken.objects.filter(user=user).delete()
    token = ExpiringToken.objects.create(user=user)
    access_token = response_json['access_token']
    has_token = False
    try:
        user_auth = UserAuth.objects.filter(user_email=user_mail).first()
        if user_auth:
            has_token = True
    except:
        print("User has no token")
    if has_token:
        UserAuth.objects.filter(user_email=user_mail).first().delete()
    UserAuth.objects.create(user_email=user_mail,
                            expiring_token="Token " + str(token),
                            access_token=access_token)
    return Response(({'token': token.key, 'email': user_mail}))
Exemplo n.º 7
0
async def get_session(client_id: str, client_secret: str) -> AuthToken:
    """
    Use the Authorization Code Grant flow to get a token.

    This opens a browser tab.
    """
    refresh_token_file = os.path.join(config.config_dir(), '.refresh.token')
    base_url = 'https://bitbucket.org/site/oauth2'

    # If we have a refresh token, use that
    existing_token = None
    if os.path.isfile(refresh_token_file):
        with open(refresh_token_file) as f:
            existing_token = json.load(f)

    now = arrow.utcnow()
    if existing_token and arrow.get(existing_token['expires_at']) - now > timedelta(minutes=5):
        log.info('Found existing token')
        return existing_token

    # Otherwise, send the user to the browser flow
    redirect_uri = 'https://localhost:8888'
    client = WebApplicationClient(client_id)
    auth_url = client.prepare_request_uri(f'{base_url}/authorize', redirect_uri=redirect_uri)

    print(f'Please go to the following link, then copy the redirected URL back here.\n\n\t{auth_url}\n')
    code = client.parse_request_uri_response(input('URL: '))['code']
    token_reqest_params = parse_qs(client.prepare_request_body(code=code, redirect_uri=redirect_uri))

    async with aiohttp.ClientSession() as session:
        resp = await session.post(
            f'{base_url}/access_token',
            headers={'Authorization': aiohttp.BasicAuth(client_id, client_secret).encode()},
            data=token_reqest_params
        )
        if resp.status != 200:
            log.error(await resp.text())
            raise Exception('Could not authenticate with the Bitbucket API')

    token: AuthToken = await resp.json()
    token['expires_at'] = now.shift(seconds=token['expires_in']).format(arrow.FORMAT_RFC3339)

    with open(refresh_token_file, 'w') as f:
        json.dump(token, f)

    return token
Exemplo n.º 8
0
def test_oauth2(app, user, oauth2_client):
    client_id, client_secret = oauth2_client
    client = WebApplicationClient(client_id)
    host = 'https://localhost'
    state = 'randomly_text'

    with app.test_client() as provider:
        # login forcefully.
        with provider.session_transaction() as sess:
            sess['user_id'] = user
            sess['_fresh'] = True
        uri = client.prepare_request_uri(host + '/oauth2/authorize',
                                         redirect_uri=redirect_uri,
                                         state=state)
        uri = uri[len(host):]
        # step 1: redirect to provider
        response = provider.get(uri, follow_redirects=True)
        assert response.status_code == 200
        # step 2: redirect to client
        response = provider.post(uri,
                                 data={
                                     'scope': 'user',
                                     'confirm': 'yes',
                                 })
        assert response.location.startswith(redirect_uri)
        data = client.parse_request_uri_response(response.location,
                                                 state=state)
        assert 'code' in data
        # step 3: get the token
        body = client.prepare_request_body(
            code=data['code'],
            redirect_uri=redirect_uri,
        )
        response = provider.post(
            '/oauth2/token',
            content_type='application/x-www-form-urlencoded',
            data=body)
        assert response.status_code == 200
        data = client.parse_request_body_response(response.data)
        assert 'access_token' in data
        assert data['token_type'] == 'Bearer'
        assert data['scope'] == ['user']
        # step 4: using token
        pass
Exemplo n.º 9
0
def index(request):
    client = WebApplicationClient(settings.GITHUB_CLIENT_ID)

    if not request.GET.has_key('code'):
        # todo - also setup hook for repo
        # todo - check http referrer = 'https://github.com/'
        uri = client.prepare_request_uri(AUTHORIZATION_URL, redirect_uri=request.build_absolute_uri(reverse('index')),
                                   scope=['repo'])
        context = {'uri': uri}
    else:
        code = request.GET['code']
        body = client.prepare_request_body(code=code, client_secret=settings.GITHUB_CLIENT_SECRET)
        headers = {'Accept': 'application/json'}
        response = requests.post(REQUEST_TOKEN_URL, body, headers=headers)
        response = response.json()
        owner = Github(response['access_token']).get_user()
        # todo - check if user already exists => show repo settings
        token = GithubToken(owner=owner.login, access_token=response['access_token'], scope=response['scope'])
        token.save()
        context = {'owner': owner.name}
    return render(request, 'benchmarks/index.html', context)
Exemplo n.º 10
0
class GoogleMusicSession(httpx.Client):
    authorization_base_url = AUTHORIZATION_BASE_URL
    redirect_uri = REDIRECT_URI
    token_url = TOKEN_URL

    def __init__(self,
                 client_id,
                 client_secret,
                 scope,
                 *,
                 token=None,
                 **kwargs):
        # httpx sets a default timeout on the Client class.
        # requests did not.
        # Disable timeout by default as too low a value
        # can cause issues with upload calls.
        timeout = kwargs.pop('timeout', None)
        super().__init__(timeout=timeout, **kwargs)

        self.params = {}
        self.headers.update({'User-Agent': f'{__title__}/{__version__}'})

        self.client_id = client_id
        self.client_secret = client_secret
        self.scope = scope

        self.token = token or {}
        self.oauth_client = WebApplicationClient(self.client_id,
                                                 token=self.token)

    @property
    def access_token(self):
        return self.token.get('access_token')

    @property
    def authorized(self):
        return bool(self.access_token)

    def authorization_url(self):
        state = generate_token()

        return (self.oauth_client.prepare_request_uri(
            self.authorization_base_url,
            redirect_uri=self.redirect_uri,
            scope=self.scope,
            state=state,
            access_type='offline',
            prompt='select_account'))

    def fetch_token(self, code):
        body = self.oauth_client.prepare_request_body(
            code=code,
            body='',
            redirect_uri=self.redirect_uri,
            include_client_id=None)

        response = self.request(
            'POST',
            self.token_url,
            headers={
                'Accept': 'application/json',
                'Content-Type':
                'application/x-www-form-urlencoded;charset=UTF-8',
            },
            data=dict(urldecode(body)),
            auth=httpx.BasicAuth(self.client_id, self.client_secret))

        self.token = self.oauth_client.parse_request_body_response(
            response.text, scope=self.scope)

        return self.token

    def refresh_token(self):
        refresh_token = self.token.get('refresh_token')

        body = self.oauth_client.prepare_refresh_body(
            body='',
            refresh_token=refresh_token,
            scope=self.scope,
            client_id=self.client_id,
            client_secret=self.client_secret)

        response = self.request(
            'POST',
            self.token_url,
            headers={
                'Accept': 'application/json',
                'Content-Type':
                'application/x-www-form-urlencoded;charset=UTF-8',
            },
            data=dict(urldecode(body)),
            auth=httpx.BasicAuth(self.client_id, self.client_secret),
            withhold_token=True)

        self.token = self.oauth_client.parse_request_body_response(
            response.text, scope=self.scope)
        if 'refresh_token' not in self.token:
            self.token['refresh_token'] = refresh_token

        return self.token

    def request(self,
                method,
                url,
                data=None,
                headers=None,
                withhold_token=False,
                **kwargs):
        if self.token and not withhold_token:
            try:
                url, headers, data = self.oauth_client.add_token(
                    url, http_method=method, body=data, headers=headers)
            except TokenExpiredError:
                self.refresh_token()
                url, headers, data = self.oauth_client.add_token(
                    url, http_method=method, body=data, headers=headers)

        return super().request(method,
                               url,
                               headers=headers,
                               data=data,
                               **kwargs)
Exemplo n.º 11
0
    def test_prepare_request_body(self):
        """
        see issue #585
            https://github.com/oauthlib/oauthlib/issues/585

        `prepare_request_body` should support the following scenarios:
            1. Include client_id alone in the body (default)
            2. Include client_id and client_secret in auth and not include them in the body (RFC preferred solution)
            3. Include client_id and client_secret in the body (RFC alternative solution)
            4. Include client_id in the body and an empty string for client_secret.
        """
        client = WebApplicationClient(self.client_id)

        # scenario 1, default behavior to include `client_id`
        r1 = client.prepare_request_body()
        self.assertEqual(r1, 'grant_type=authorization_code&client_id=%s' % self.client_id)

        r1b = client.prepare_request_body(include_client_id=True)
        self.assertEqual(r1b, 'grant_type=authorization_code&client_id=%s' % self.client_id)

        # scenario 2, do not include `client_id` in the body, so it can be sent in auth.
        r2 = client.prepare_request_body(include_client_id=False)
        self.assertEqual(r2, 'grant_type=authorization_code')

        # scenario 3, Include client_id and client_secret in the body (RFC alternative solution)
        # the order of kwargs being appended is not guaranteed. for brevity, check the 2 permutations instead of sorting
        r3 = client.prepare_request_body(client_secret=self.client_secret)
        r3_params = dict(urlparse.parse_qsl(r3, keep_blank_values=True))
        self.assertEqual(len(r3_params.keys()), 3)
        self.assertEqual(r3_params['grant_type'], 'authorization_code')
        self.assertEqual(r3_params['client_id'], self.client_id)
        self.assertEqual(r3_params['client_secret'], self.client_secret)

        r3b = client.prepare_request_body(include_client_id=True, client_secret=self.client_secret)
        r3b_params = dict(urlparse.parse_qsl(r3b, keep_blank_values=True))
        self.assertEqual(len(r3b_params.keys()), 3)
        self.assertEqual(r3b_params['grant_type'], 'authorization_code')
        self.assertEqual(r3b_params['client_id'], self.client_id)
        self.assertEqual(r3b_params['client_secret'], self.client_secret)

        # scenario 4, `client_secret` is an empty string
        r4 = client.prepare_request_body(include_client_id=True, client_secret='')
        r4_params = dict(urlparse.parse_qsl(r4, keep_blank_values=True))
        self.assertEqual(len(r4_params.keys()), 3)
        self.assertEqual(r4_params['grant_type'], 'authorization_code')
        self.assertEqual(r4_params['client_id'], self.client_id)
        self.assertEqual(r4_params['client_secret'], '')

        # scenario 4b, `client_secret` is `None`
        r4b = client.prepare_request_body(include_client_id=True, client_secret=None)
        r4b_params = dict(urlparse.parse_qsl(r4b, keep_blank_values=True))
        self.assertEqual(len(r4b_params.keys()), 2)
        self.assertEqual(r4b_params['grant_type'], 'authorization_code')
        self.assertEqual(r4b_params['client_id'], self.client_id)

        # scenario Warnings
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")  # catch all

            # warning1 - raise a DeprecationWarning if a `client_id` is submitted
            rWarnings1 = client.prepare_request_body(client_id=self.client_id)
            self.assertEqual(len(w), 1)
            self.assertIsInstance(w[0].message, DeprecationWarning)

            # testing the exact warning message in Python2&Python3 is a pain

        # scenario Exceptions
        # exception1 - raise a ValueError if the a different `client_id` is submitted
        with self.assertRaises(ValueError) as cm:
            client.prepare_request_body(client_id='different_client_id')
    def get(self, request, *args, **kwargs):

        # Retrieve these data from the URL
        data = self.request.GET
        code = data['code']
        state = data['state']
        print("code=%s, state=%s" % (code, state))

        # For security purposes, verify that the
        # state information is the same as was passed
        # to github_login()
        if self.request.session['state'] != state:
            messages.add_message(self.request, messages.ERROR,
                                 "State information mismatch!")
            return HttpResponseRedirect(reverse('github:welcome'))
        else:
            del self.request.session['state']

        # fetch the access token from GitHub's API at token_url
        token_url = 'https://github.com/login/oauth/access_token'
        client_id = settings.GITHUB_OAUTH_CLIENT_ID
        client_secret = settings.GITHUB_OAUTH_SECRET

        # Create a Web Applicantion Client from oauthlib
        client = WebApplicationClient(client_id)

        # Prepare body for request
        data = client.prepare_request_body(
            code=code,
            redirect_uri=settings.GITHUB_OAUTH_CALLBACK_URL,
            client_id=client_id,
            client_secret=client_secret)

        # Post a request at GitHub's token_url
        # Returns requests.Response object
        response = requests.post(token_url, data=data)
        """
    Parse the unicode content of the response object
    Returns a dictionary stored in client.token
    {
      'access_token': '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',
      'scope': ['read:user'],
      'token_type': 'bearer'
    }
    """
        client.parse_request_body_response(response.text)

        # Prepare an Authorization header for GET request using the 'access_token' value
        # using GitHub's official API format
        header = {
            'Authorization': 'token {}'.format(client.token['access_token'])
        }

        # Retrieve GitHub profile data
        # Send a GET request
        # Returns requests.Response object
        response = requests.get('https://api.github.com/user', headers=header)

        # Store profile data in JSON
        json_dict = response.json()
        '''
    Fields that are of interest:
      'login' => json_dict['login'],
      'name' => json_dict['name'],
      'bio' => json_dict['bio'],
      'blog' => json_dict['blog'],
      'email' => json_dict['email'],    # not public data
      'avatar_url' => json_dict['avatar_url'],
    '''

        # save the user profile in a session
        self.request.session['profile'] = json_dict

        # retrieve or create a Django User for this profile
        try:
            user = User.objects.get(username=json_dict['login'])

            messages.add_message(
                self.request, messages.DEBUG,
                "User %s already exists, Authenticated? %s" %
                (user.username, user.is_authenticated))

            print("User %s already exists, Authenticated %s" %
                  (user.username, user.is_authenticated))

            # remember to log the user into the system
            login(self.request, user)

        except:
            # create a Django User for this login
            user = User.objects.create_user(json_dict['login'],
                                            json_dict['email'])

            messages.add_message(
                self.request, messages.DEBUG,
                "User %s is created, Authenticated %s?" %
                (user.username, user.is_authenticated))

            print("User %s is created, Authenticated %s" %
                  (user.username, user.is_authenticated))

            # remember to log the user into the system
            login(self.request, user)

        # Redirect response to hide the callback url in browser
        return HttpResponseRedirect(reverse('github:welcome'))
Exemplo n.º 13
0
    def test_prepare_request_body(self):
        """
        see issue #585
            https://github.com/oauthlib/oauthlib/issues/585

        `prepare_request_body` should support the following scenarios:
            1. Include client_id alone in the body (default)
            2. Include client_id and client_secret in auth and not include them in the body (RFC preferred solution)
            3. Include client_id and client_secret in the body (RFC alternative solution)
            4. Include client_id in the body and an empty string for client_secret.
        """
        client = WebApplicationClient(self.client_id)

        # scenario 1, default behavior to include `client_id`
        r1 = client.prepare_request_body()
        self.assertEqual(r1, 'grant_type=authorization_code&client_id=%s' % self.client_id)

        r1b = client.prepare_request_body(include_client_id=True)
        self.assertEqual(r1b, 'grant_type=authorization_code&client_id=%s' % self.client_id)

        # scenario 2, do not include `client_id` in the body, so it can be sent in auth.
        r2 = client.prepare_request_body(include_client_id=False)
        self.assertEqual(r2, 'grant_type=authorization_code')

        # scenario 3, Include client_id and client_secret in the body (RFC alternative solution)
        # the order of kwargs being appended is not guaranteed. for brevity, check the 2 permutations instead of sorting
        r3 = client.prepare_request_body(client_secret=self.client_secret)
        r3_params = dict(urlparse.parse_qsl(r3, keep_blank_values=True))
        self.assertEqual(len(r3_params.keys()), 3)
        self.assertEqual(r3_params['grant_type'], 'authorization_code')
        self.assertEqual(r3_params['client_id'], self.client_id)
        self.assertEqual(r3_params['client_secret'], self.client_secret)

        r3b = client.prepare_request_body(include_client_id=True, client_secret=self.client_secret)
        r3b_params = dict(urlparse.parse_qsl(r3b, keep_blank_values=True))
        self.assertEqual(len(r3b_params.keys()), 3)
        self.assertEqual(r3b_params['grant_type'], 'authorization_code')
        self.assertEqual(r3b_params['client_id'], self.client_id)
        self.assertEqual(r3b_params['client_secret'], self.client_secret)

        # scenario 4, `client_secret` is an empty string
        r4 = client.prepare_request_body(include_client_id=True, client_secret='')
        r4_params = dict(urlparse.parse_qsl(r4, keep_blank_values=True))
        self.assertEqual(len(r4_params.keys()), 3)
        self.assertEqual(r4_params['grant_type'], 'authorization_code')
        self.assertEqual(r4_params['client_id'], self.client_id)
        self.assertEqual(r4_params['client_secret'], '')

        # scenario 4b, `client_secret` is `None`
        r4b = client.prepare_request_body(include_client_id=True, client_secret=None)
        r4b_params = dict(urlparse.parse_qsl(r4b, keep_blank_values=True))
        self.assertEqual(len(r4b_params.keys()), 2)
        self.assertEqual(r4b_params['grant_type'], 'authorization_code')
        self.assertEqual(r4b_params['client_id'], self.client_id)

        # scenario Warnings
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")  # catch all

            # warning1 - raise a DeprecationWarning if a `client_id` is submitted
            rWarnings1 = client.prepare_request_body(client_id=self.client_id)
            self.assertEqual(len(w), 1)
            self.assertIsInstance(w[0].message, DeprecationWarning)

            # testing the exact warning message in Python2&Python3 is a pain

        # scenario Exceptions
        # exception1 - raise a ValueError if the a different `client_id` is submitted
        with self.assertRaises(ValueError) as cm:
            client.prepare_request_body(client_id='different_client_id')
Exemplo n.º 14
0
class OAuthClient:
    """
    Helper class to handle the OAuth authentication flow
    the logic is divided in 2 steps:
    - open the browser on GitGuardian login screen and run a local server to wait for callback
    - handle the oauth callback to exchange an authorization code against a valid access token
    """
    def __init__(self, config: Config, instance: str) -> None:
        self.config = config
        self.instance = instance
        self._oauth_client = WebApplicationClient(CLIENT_ID)
        self._state = ""  # use the `state` property instead

        self._handler_wrapper = RequestHandlerWrapper(oauth_client=self)
        self._access_token: Optional[str] = None
        self._port = USABLE_PORT_RANGE[0]
        self.server: Optional[HTTPServer] = None

        self._generate_pkce_pair()

    def oauth_process(self,
                      token_name: Optional[str] = None,
                      lifetime: Optional[int] = None) -> None:
        """
        Handle the whole oauth process which includes
        - opening the user's webbrowser to GitGuardian login page
        - open a server and wait for the callback processing
        """
        # enable redirection to http://localhost
        os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = str(True)

        if token_name is None:
            token_name = "ggshield token " + datetime.today().strftime(
                "%Y-%m-%d")
        self._token_name = token_name

        if lifetime is None:
            lifetime = self.default_token_lifetime
        self._lifetime = lifetime

        self._prepare_server()
        self._redirect_to_login()
        self._wait_for_callback()

        message = f"Created Personal Access Token {self._token_name} "
        expire_at = self.instance_config.account.expire_at
        if expire_at is not None:
            message += "expiring on " + get_pretty_date(expire_at)
        else:
            message += "with no expiry"
        click.echo(message)

    def process_callback(self, callback_url: str) -> None:
        """
        This function runs within the request handler do_GET method.
        It takes the url of the callback request as argument and does
        - Extract the authorization code
        - Exchange the code against an access token with GitGuardian's api
        - Validate the new token against GitGuardian's api
        - Save the token in configuration
        Any error during this process will raise a OAuthError
        """
        authorization_code = self._get_code(callback_url)
        self._claim_token(authorization_code)
        token_data = self._validate_access_token()
        self._save_token(token_data)

    def _generate_pkce_pair(self) -> None:
        """
        Generate a code verifier (random string) and its sha encoded version to be used
        for the pkce checking process
        """
        self.code_verifier = self._oauth_client.create_code_verifier(
            128)  # type: ignore
        self.code_challenge = (urlsafe_b64encode(
            sha256(self.code_verifier.encode()).digest()).decode().rstrip("="))

    def _redirect_to_login(self) -> None:
        """
        Open the user's browser to the GitGuardian ggshield authentication page
        """
        static_params = {
            "auth_mode": "ggshield_login",
            "utm_source": "cli",
            "utm_medium": "login",
            "utm_campaign": "ggshield",
        }
        request_uri = self._oauth_client.prepare_request_uri(
            uri=urlparse.urljoin(self.dashboard_url, "auth/login"),
            redirect_uri=self.redirect_uri,
            scope=SCOPE,
            code_challenge=self.code_challenge,
            code_challenge_method="S256",
            state=self.state,
            **static_params,
        )
        click.echo(
            f"To complete the login process, follow the instructions from {request_uri}.\n"
            "Opening your web browser now...")
        webbrowser.open_new_tab(request_uri)

    def _prepare_server(self) -> None:
        for port in range(*USABLE_PORT_RANGE):
            try:
                self.server = HTTPServer(
                    # only consider requests from localhost on the predetermined port
                    ("127.0.0.1", port),
                    # attach the wrapped request handler
                    self._handler_wrapper.request_handler,
                )
                self._port = port
                break
            except OSError:
                continue
        else:
            raise click.ClickException("Could not find unoccupied port.")

    def _wait_for_callback(self) -> None:
        """
        Wait to receive and process the authorization callback on the local server.
        This actually catches HTTP requests made on the previously opened server.
        The callback processing logic is implementend in the request handler class
        and the `process_callback` method
        """
        try:
            while not self._handler_wrapper.complete:
                # Wait for callback on localserver including an authorization code
                # any matchin request will get processed by the request handler and
                # the `process_callback` function
                self.server.handle_request()  # type: ignore
        except KeyboardInterrupt:
            raise click.ClickException("Aborting")

        if self._handler_wrapper.error_message is not None:
            # if no error message is attached, the process is considered successful
            raise click.ClickException(self._handler_wrapper.error_message)

    def _get_code(self, uri: str) -> str:
        """
        Extract the authorization from the incoming request uri and verify that the state from
        the uri match the one stored internally.
        if no code can be extracted or the state is invalid, raise an OAuthError
        else return the extracted code
        """
        try:
            authorization_code = self._oauth_client.parse_request_uri_response(
                uri, self.state).get("code")
        except OAuth2Error:
            authorization_code = None
        if authorization_code is None:
            raise OAuthError(
                "Invalid code or state received from the callback.")
        return authorization_code  # type: ignore

    def _claim_token(self, authorization_code: str) -> None:
        """
        Exchange the authorization code with a valid access token using GitGuardian public api.
        If no valid token could be retrieved, exit the authentication process with an error message
        """

        request_params = {"name": self._token_name}
        if self._lifetime is not None:
            request_params["lifetime"] = str(self._lifetime)

        request_body = self._oauth_client.prepare_request_body(
            code=authorization_code,
            redirect_uri=self.redirect_uri,
            code_verifier=self.code_verifier,
            body=urlparse.urlencode(request_params),
        )

        response = requests.post(
            urlparse.urljoin(self.api_url, "oauth/token"),
            request_body,
            headers={"Content-Type": "application/x-www-form-urlencoded"},
        )

        if not response.ok:
            raise OAuthError("Cannot create a token.")

        self._access_token = response.json()["key"]
        self.config.auth_config.current_token = self._access_token

    def _validate_access_token(self) -> Dict[str, Any]:
        """
        Validate the token using GitGuardian public api.
        If the token is not valid, exit the authentication process with an error message.
        """
        response = retrieve_client(self.config).get(endpoint="token")
        if not response.ok:
            raise OAuthError("The created token is invalid.")
        return response.json()  # type: ignore

    def _save_token(self, api_token_data: Dict[str, Any]) -> None:
        """
        Save the new token in the configuration.
        """
        account_config = AccountConfig(
            account_id=api_token_data["account_id"],
            token=self._access_token,  # type: ignore
            expire_at=api_token_data.get("expire_at"),
            token_name=api_token_data.get("name", ""),
            type=api_token_data.get("type", ""),
        )
        self.instance_config.account = account_config
        self.config.save()

    @property
    def instance_config(self) -> InstanceConfig:
        return self.config.auth_config.instances[self.instance]

    @property
    def default_token_lifetime(self) -> Optional[int]:
        """
        return the default token lifetime saved in the instance config.
        if None, this will be interpreted as no expiry.
        """
        default_lifetime = self.instance_config.default_token_lifetime
        if default_lifetime is not None:
            return default_lifetime.days

        return None

    @property
    def redirect_uri(self) -> str:
        return f"http://localhost:{self._port}"

    @property
    def state(self) -> str:
        """
        Return the state used to verify the auth process.
        The state is included in the redirect_uri and is expected in the callback url.
        Then, if both states don't match, the process fails.
        The state is an url-encoded string dict containing the token name and lifetime
        It is cached to prevent from altering its value during the process
        """
        if not self._state:
            self._state = urlparse.quote(
                json.dumps({
                    "token_name": self._token_name,
                    "lifetime": self._lifetime
                }))
        return self._state

    @property
    def dashboard_url(self) -> str:
        return self.config.dashboard_url

    @property
    def api_url(self) -> str:
        return self.config.api_url
Exemplo n.º 15
0
def callback(request):
    #parse the incomming answer from oauth
    client = WebApplicationClient(settings.SHEN_RING_CLIENT_ID)
    try:
        response = client.parse_request_uri_response(request.build_absolute_uri())
    except MissingCodeError:
        if 'error' in request.GET:
            raise PermissionDenied(request.GET.get('error'))
        else:
            raise PermissionDenied("Authentication failed")

    if not settings.SHEN_RING_NO_CSRF:
        if 'state' not in response:
            raise PermissionDenied("csrf_state_check_failed")

        if request.session.get(csrf.CSRF_SESSION_KEY, '') != response['state']:
            raise PermissionDenied("csrf_state_check_failed")

    #upgrade grant code to access code
    session = requests.Session()
    session.headers['User-Agent'] = 'BEP Marketplace ELE'
    #get parameters
    data = client.prepare_request_body(code=response['code'], client_secret=settings.SHEN_RING_CLIENT_SECRET,
                                include_client_id=True)
    # convert to requests dictionary
    data_dict = {}
    for itm in data.split("&"):
        data_dict[itm.split('=')[0]] = itm.split('=')[1]

    #request accesstoken
    try:
        access_code_data = requests.post(settings.SHEN_RING_URL + "oauth/token/", data=data_dict).json()
    except:
        raise PermissionDenied("invalid_json_data")
    if 'access_token' not in access_code_data:
        raise PermissionDenied(access_code_data['error'])

    #request account information
    ## this assumes that timeslot pk is identical on both shen and local db!
    r = session.get(settings.SHEN_RING_URL + "info/", headers={"Authorization" : "Bearer {}".format(access_code_data["access_token"])})

    if r.status_code != 200:
        raise PermissionDenied("shen_link_failed")

    try:
        value = json.dumps(signing.loads(r.text, settings.SHEN_RING_CLIENT_SECRET))
    except signing.BadSignature:
        raise PermissionDenied("shen_signing_failed")


    #login or create the user
    try:
        user, usermeta = serializers.deserialize('json', value)
    except:
        raise PermissionDenied('corrupted_user_info_retrieved')
    ## data from info is directly saved to db, this means that the appointed shen system is fully trusted
    ##  this is breached when the shen server is man in the middled, but then an attacker needs to steal both the domain as well as the secret keys


    if User.objects.filter(Q(username=user.object.username) & Q(email=user.object.email)).count() == 1:
        #user exists
        existent_user = User.objects.filter(Q(username=user.object.username) & Q(email=user.object.email))[0]
        user.object.pk = existent_user.pk
        existent_usermeta = existent_user.usermeta
        usermeta.object.pk = existent_usermeta.pk
        groups = list(existent_user.groups.all())
        timeslots = list(existent_usermeta.TimeSlot.all())

        # for fields that do not exist on shen but do exist on local, port the value over otherwise data is lost
        for local_field in settings.USERMETA_LOCAL_FIELDS:
            setattr(usermeta.object, local_field, getattr(existent_usermeta, local_field))
        try:
            user.save()
            usermeta.object.User = user.object
            usermeta.save()
        except:
            raise PermissionDenied("Authentication failed")
        #overwrite the timeslots, this needs to be done after usermeta save due to begin a m2m relation
        usermeta.object.TimeSlot.clear()
        for ts in timeslots:
            usermeta.object.TimeSlot.add(ts)
        usermeta.object.save()
        #foreignkeys on the user to other models are wiped with this method, foreignkeys from other models to user keep working
        # has to be done after save because its an m2m relation
        for group in groups:
            user.object.groups.add(group)
        user.object.save()
    elif User.objects.filter(Q(username=user.object.username) & Q(email=user.object.email)).count() == 0:
        #user does not exist
        user.object.pk = None
        usermeta.object.pk = None
        try:
            user.save()
            usermeta.object.User = user.object
            usermeta.save()
            # clear timeslots as this is handled internally
            usermeta.object.TimeSlot.clear()
            usermeta.save()
        except:
            raise PermissionDenied("Authentication failed")
    else:
        #more then one user found with this combination, db corrupted, abort
        return HttpResponseNotFound()
    user = user.object
    usermeta = usermeta.object
    response = check_user(request, user)
    if response is not True:
        return response

    #login user
    auth.login(request, user)

    #TODO: fix that next parameter is taken into account
    return HttpResponseRedirect("/")
Exemplo n.º 16
0
def callback(request):
    # parse the incoming answer from oauth
    client = WebApplicationClient(settings.SHEN_RING_CLIENT_ID)
    try:
        response = client.parse_request_uri_response(
            request.build_absolute_uri())
    except MissingCodeError:
        if 'error' in request.GET:
            raise PermissionDenied(request.GET.get('error'))
        else:
            raise PermissionDenied("Authentication failed")

    if not settings.SHEN_RING_NO_CSRF:
        if 'state' not in response:
            raise PermissionDenied(
                "Authentication failed. (csrf state not available)")

        if '-' in response['state']:
            csrf_token, next_url = response['state'].split('-')
            next_url = base64.b64decode(next_url.encode()).decode()
        else:
            csrf_token = response['state']
            next_url = None

        if request.session.get(csrf.CSRF_SESSION_KEY, '') != csrf_token:
            raise PermissionDenied(
                "Authentication failed. (csrf token failed)")
    else:
        if '-' in response.get('state', ""):
            next_url = base64.b64decode(
                response['state'].strip('-').encode()).decode()
        else:
            next_url = None

    # upgrade grant code to access code
    session = requests.Session()
    session.headers['User-Agent'] = settings.NAME_PRETTY
    # get parameters
    data = client.prepare_request_body(
        code=response['code'],
        client_secret=settings.SHEN_RING_CLIENT_SECRET,
        include_client_id=True)
    # convert to requests dictionary
    data_dict = {}
    for itm in data.split("&"):
        data_dict[itm.split('=')[0]] = itm.split('=')[1]

    # request accesstoken
    try:
        access_code_data = requests.post(settings.SHEN_RING_URL +
                                         "oauth/token/",
                                         data=data_dict).json()
    except:
        raise PermissionDenied("Authentication failed. (invalid_json_data)")
    if 'access_token' not in access_code_data:
        raise PermissionDenied(access_code_data['error'])

    # request account information
    # this assumes that timeslot pk is identical on both shen and local db!
    r = session.get(settings.SHEN_RING_URL + "info/",
                    headers={
                        "Authorization":
                        "Bearer {}".format(access_code_data["access_token"])
                    })

    if r.status_code != 200:
        raise PermissionDenied("Authentication failed. (shen_link_failed)")

    try:
        value = json.dumps(
            signing.loads(r.text, settings.SHEN_RING_CLIENT_SECRET))
    except signing.BadSignature:
        raise PermissionDenied("Authentication failed. (shen_signing_failed)")

    # login or create the user
    try:
        user, usermeta = serializers.deserialize('json', value)
    except:
        raise PermissionDenied(
            'Authentication failed. (corrupted_user_info_retrieved)')
    # data from info is directly saved to db, this means that the appointed shen system is fully trusted
    #  this is breached when the shen server is man in the middled, but then an attacker needs to steal both the domain as well as the secret keys

    # find user and map shen user to local user
    existent_user = get_user(user.object.email, user.object.username)
    if existent_user:
        if not existent_user.is_active:
            raise PermissionDenied(
                "Your user account is disabled. Please contact support.")
        user.object.pk = existent_user.pk
        existent_usermeta = existent_user.usermeta
        usermeta.object.pk = existent_usermeta.pk
        groups = list(existent_user.groups.all())
        perms = list(existent_user.user_permissions.all())
        timeslots = list(existent_usermeta.TimeSlot.all())
        user.object.is_staff = existent_user.is_staff  # keep the is_staff flag.
        # for fields that do not exist on shen but do exist on local, port the value over otherwise data is lost
        for local_field in settings.USERMETA_LOCAL_FIELDS:
            setattr(usermeta.object, local_field,
                    getattr(existent_usermeta, local_field))
        try:
            user.save()
            usermeta.object.User = user.object
            usermeta.save()
        except Exception as e:
            logger.exception(
                'User save failed with Shen login for existing user {}. Exception {}'
                .format(user, e))
            raise PermissionDenied(
                "Something went wrong while logging you in. Please contact support at {} to get this resolved."
                .format(settings.CONTACT_EMAIL))
        # overwrite the timeslots, this needs to be done after usermeta save due to begin a m2m relation
        usermeta.object.TimeSlot.clear()
        for ts in timeslots:
            usermeta.object.TimeSlot.add(ts)
        usermeta.object.save()
        # foreignkeys on the user to other models are wiped with this method, foreignkeys from other models to user keep working
        # has to be done after save because its an m2m relation
        for group in groups:
            user.object.groups.add(group)
        for perm in perms:  # copy custom user permissions to have individual admin access for users.
            user.object.user_permissions.add(perm)
        user.object.save()
    elif existent_user is None:
        # user does not exist
        user.object.pk = None
        usermeta.object.pk = None
        try:
            user.save()
            usermeta.object.User = user.object
            usermeta.save()
            # clear timeslots as this is handled internally
            usermeta.object.TimeSlot.clear()
            usermeta.save()
        except Exception as e:
            logger.exception(
                'User save failed with Shen login for new user {}. Exception {}'
                .format(user, e))
            raise PermissionDenied(
                "Something went wrong while logging you in. Please contact support at {} to get this resolved."
                .format(settings.CONTACT_EMAIL))
    else:
        # more then one user found with this combination, db corrupted, abort
        # this will not happen, as get_user already raises exception
        return HttpResponseNotFound()
    user = user.object
    usermeta = usermeta.object
    response = check_user(request, user)
    if response is not True:
        return response

    # login user
    auth.login(request, user)

    if next_url is not None:
        if is_safe_url(next_url, None):
            return HttpResponseRedirect(next_url)

    return HttpResponseRedirect("/")