Metadata-Version: 2.1
Name: id4me-rp-client
Version: 0.0.23
Summary: Python client library for ID4me protocol - Relying Party side. See: https://id4me.org
Home-page: https://gitlab.com/ID4me/id4me-rp-client-python
Author: Pawel Kowalik
Author-email: pawel-kow@users.noreply.github.com
License: https://gitlab.com/ID4me/id4me-rp-client-python/blob/master/LICENSE
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Description-Content-Type: text/markdown
Requires-Dist: dnspython (>=1.15.0)
Requires-Dist: six (>=1.11.0)
Requires-Dist: future (>=0.16.0)
Requires-Dist: jwcrypto (>=0.5.0)

# id4me-rp-client
Python Relying Party client library for ID4me protocol.
For details of the protocol, please visit: https://id4me.org

Library offers Relying Party functionality for authentication with Identity Authority and claim request from the Identity Agent..

## Specification reference
https://gitlab.com/ID4me/documentation/blob/master/id4ME%20Technical%20Specification.adoc
- Version: 1.0
- Revision: 02

## Installation
```shell
pip install id4me-rp-client
```

## Usage

### Register the client and authorize with Identity Authority

```python
from id4me_rp_client import *

# these imports are just needed in this example
from builtins import input
import json
import uuid

registrations = dict()

# a routine to save client registration at authority
def save_authority_registration(auth_name, auth_content):
    registrations[auth_name] = auth_content
    pass


# a routine to load client registration at authority
def load_authority_registration(auth_name):
    return registrations[auth_name]

# create client object with basic parameters of your app
client = ID4meClient(
    get_client_registration=load_authority_registration,
    save_client_registration=save_authority_registration,
    app_type=OIDCApplicationType.web,
    validate_url='https://dynamicdns.domainconnect.org/ddnscode',
    client_name='Foo app',
    logo_url='https://upload.wikimedia.org/wikipedia/commons/7/76/Foobar2000_logo_2014.png',
    policy_url='https://foo.com/policy',
    tos_url='https://foo.com/tos',
    private_jwks_json=ID4meClient.generate_new_private_keys_set())

try:
    # make a discovery of identity authority and register if needed
    # find_authority and save_authority are optional, but when missing client will be registered each time anew
    ctx = client.get_rp_context(
        id4me='id200.connect.domains')    

    # get a link to login routine
    link = client.get_consent_url(
        ctx,
        claimsrequest=ID4meClaimsRequest(
            userinfo_claims={
                OIDCClaim.name: ID4meClaimRequestProperties(reason='To call you by name'),
                OIDCClaim.email: ID4meClaimRequestProperties(essential=True, reason='To be able to contact you'),
                OIDCClaim.email_verified: ID4meClaimRequestProperties(reason='To know if your E-mail was verified'),
            }),
        state=str(uuid.uuid4())
        )
    print('Please open the link:\n{}'.format(link))

    # Normally code will arrive as query param on client.validateUrl
    code = input('Please enter code: ')
    # Get ID token
    id_token = client.get_idtoken(context=ctx, code=code)
    print('ID Token:\n{}'.format(json.dumps(id_token, sort_keys=True, indent=4)))
    # Get User Info
    userinfo = client.get_user_info(context=ctx)
    print('User Info:\n{}'.format(json.dumps(userinfo, sort_keys=True, indent=4)))    
except ID4meException as e:
    print('Exception: {}'.format(e))

```

#### Output:
```text
Resolving "_openid.id200.connect.domains."
Checking TXT record "v=OID1;iss=id.test.denic.de;clp=identityagent.de"
identity_authority = auth.freedom-id.de
registering with new identity authority (auth.freedom-id.de)
destination = https://auth.freedom-id.de/login?scope=openid&response_type=code&client_id=hmkzay2riyon4&redirect_uri=https%3A//foo.com/validate&login_hint=id200.connect.domains&state=&claims=%7B%22userinfo%22%3A%20%7B%22email_verified%22%3A%20%7B%22reason%22%3A%20%22To%20know%20if%20your%20E-mail%20was%20verified%22%7D%2C%20%22email%22%3A%20%7B%22reason%22%3A%20%22To%20be%20able%20to%20contact%20you%22%2C%20%22essential%22%3A%20true%7D%2C%20%22name%22%3A%20%7B%22reason%22%3A%20%22To%20call%20you%20by%20name%22%7D%7D%7D
Please open the link:
https://auth.freedom-id.de/login?scope=openid&response_type=code&client_id=hmkzay2riyon4&redirect_uri=https%3A//foo.com/validate&login_hint=id200.connect.domains&state=&claims=%7B%22userinfo%22%3A%20%7B%22email_verified%22%3A%20%7B%22reason%22%3A%20%22To%20know%20if%20your%20E-mail%20was%20verified%22%7D%2C%20%22email%22%3A%20%7B%22reason%22%3A%20%22To%20be%20able%20to%20contact%20you%22%2C%20%22essential%22%3A%20true%7D%2C%20%22name%22%3A%20%7B%22reason%22%3A%20%22To%20call%20you%20by%20name%22%7D%7D%7D
Please enter code: >? 9jNXCX9OZ4HQLr2YZWKisw.5mSDkoR-5YJQoTp3f1vuxg
User Info:
{
    "aud": "hmkzay2riyon4", 
    "email": "foo@bar.de", 
    "email_verified": true, 
    "exp": 1538762218, 
    "iat": 1538761918, 
    "id4me.identifier": "id200.connect.domains", 
    "id4me.identity": "id200.connect.domains", 
    "iss": "https://auth.freedom-id.de", 
    "nbf": 1538761918, 
    "sub": "uiw3pTRRLVaKJqbnbSwr4EVuhEPTHvRgci91RbhYU2rab/YVDqDmqTKzTVAdDMm+", 
    "updated_at": 1538564738
}
```

### Requesting custom claims

In order to request a custom claim, it's enough to pass its name as a key in `userinfo_claims` or `id_token_claims`
parameters of `ID4meClient.get_consent_url` method.

Example
```python
...
        link = client.get_consent_url(
            ctx=context, 
            claimsrequest=ID4meClaimsRequest(
                userinfo_claims={
                    OIDCClaim.email: ID4meClaimRequestProperties(essential=True, reason='Test other confusing reason'),
                    'id4me.custom': ID4meClaimRequestProperties(essential=True, reason='Custom claim reason')
                })
        )
...
```

## CHANGELOG:
| version | date       | changes |
| ------- | -----------| ------ |
| 0.0.23  | 2019-10-04 | NEW FEATURE: plain JSON user info support added<br>NEW FEATURE: use of scope parameter instead of claims if not supported by IdP<br>NEW: example code included |
| 0.0.22  | 2019-07-29 | BUGFIX: id4me_rp_client.helper not exported to the release library |
| 0.0.21  | 2019-07-29 | BUGFIX: YXDOMAIN case not properly handled<br>BUGFIX: avoid trying to resolve empty domain names<br>BUGFIX: added better handling when state is empty<br>LOGGING: added logging of all exceptions (debug level) |
| 0.0.20  | 2019-05-23 | NEW FEATURE: E-mail address hashing as per spec change proposed in https://gitlab.com/ID4me/documentation/merge_requests/7<br>MINOR CHANGE: finally deprecated `preferred_client_id` from registration<br>NEW FEATURE: timeout configurable via NetworkConfig |
| 0.0.19  | 2019-03-24 | TEST: added Kopano to the integration test<br>BUGFIX: leeway to re-register set to 5 minutes istead of 2 hours<br>TEST: added password to mojeid test account |
| 0.0.18  | 2019-03-23 | NEW FEATURE: added support for E-mail like identifiers (just replace @ with .)<br>NEW FEATURE: requesting claims with scope<br>WORKAROUND: accepting token_type as 'Bearer' and 'bearer'<br>BUGFIX: 'tos_uri' assigned properly |
| 0.0.17  | 2019-03-19 | SECURITY FIX: Limited timeouts and size of downloaded data (DOS prevention)<br>SECURITY FIX: Limited recoursion level of distributed claims (DOS prevention) |
| 0.0.16  | 2019-03-11 | MAJOR CHANGE: removed back-compatibility with old _openid record format |
| 0.0.15  | 2019-02-27 | - NEW FEATURE: Automatically re-register expired client registration <br> - explicit parameter to enable/block automatic client registration |
| 0.0.14  | 2019-02-25 | No functional changes. Example code in README fixed |
| 0.0.13  | 2019-02-25 | No functional changes. TEST & EXAMPLE for custom claims added |
| 0.0.12  | 2019-02-21 | BUGFIX: Exception when no encryption used but private key missing |
| 0.0.11  | 2019-02-21 | BUGFIX, error when serializing ID4meContext |
| 0.0.10  | 2019-02-18 | API BREAKING CHANGE: client configuration loading callback moved to client object in order to remove secret data from the ID4meContext which can be in some frameworks sent over cookies |


