diff --git a/ereuse_devicehub/config.py b/ereuse_devicehub/config.py
index 2f23a14a..2ffca3b3 100644
--- a/ereuse_devicehub/config.py
+++ b/ereuse_devicehub/config.py
@@ -110,3 +110,5 @@ class DevicehubConfig(Config):
if API_DLT:
API_DLT = API_DLT.strip("/")
+ WALLET_INX_EBSI_PLUGIN_TOKEN = config('WALLET_INX_EBSI_PLUGIN_TOKEN', None)
+ WALLET_INX_EBSI_PLUGIN_URL = config('WALLET_INX_EBSI_PLUGIN_URL', None)
diff --git a/ereuse_devicehub/modules/did/templates/anonymous.html b/ereuse_devicehub/modules/did/templates/anonymous.html
index d0c03229..33e42fb8 100644
--- a/ereuse_devicehub/modules/did/templates/anonymous.html
+++ b/ereuse_devicehub/modules/did/templates/anonymous.html
@@ -227,7 +227,7 @@
{% if oidc %}
- User of other inventory
+ Use a wallet
{% endif %}
diff --git a/ereuse_devicehub/modules/did/views.py b/ereuse_devicehub/modules/did/views.py
index 1999eb1b..63aea371 100644
--- a/ereuse_devicehub/modules/did/views.py
+++ b/ereuse_devicehub/modules/did/views.py
@@ -60,6 +60,8 @@ class DidView(View):
tlmp = {
"isOperator": "operator.html",
"isVerifier": "verifier.html",
+ "operator": "operator.html",
+ "verifier": "verifier.html",
}
self.template_name = tlmp.get(rol, self.template_name)
diff --git a/ereuse_devicehub/modules/oidc/migrations/versions/96092022dadb_code2roles.py b/ereuse_devicehub/modules/oidc/migrations/versions/96092022dadb_code2roles.py
new file mode 100644
index 00000000..5f05b771
--- /dev/null
+++ b/ereuse_devicehub/modules/oidc/migrations/versions/96092022dadb_code2roles.py
@@ -0,0 +1,53 @@
+"""code2roles
+
+Revision ID: 96092022dadb
+Revises: abba37ff5c80
+Create Date: 2023-12-12 18:45:45.324285
+
+"""
+import citext
+import sqlalchemy as sa
+from alembic import context, op
+from sqlalchemy.dialects import postgresql
+
+
+# revision identifiers, used by Alembic.
+revision = '96092022dadb'
+down_revision = 'abba37ff5c80'
+branch_labels = None
+depends_on = None
+
+
+def get_inv():
+ INV = context.get_x_argument(as_dictionary=True).get('inventory')
+ if not INV:
+ raise ValueError("Inventory value is not specified")
+ return INV
+
+
+def upgrade():
+ op.create_table(
+ 'code_roles',
+ sa.Column('id', sa.BigInteger(), nullable=False),
+ sa.Column(
+ 'updated',
+ sa.TIMESTAMP(timezone=True),
+ server_default=sa.text('CURRENT_TIMESTAMP'),
+ nullable=False,
+ ),
+ sa.Column(
+ 'created',
+ sa.TIMESTAMP(timezone=True),
+ server_default=sa.text('CURRENT_TIMESTAMP'),
+ nullable=False,
+ ),
+ sa.Column('code', citext.CIText(), nullable=False),
+ sa.Column('roles', citext.CIText(), nullable=False),
+ schema=f'{get_inv()}',
+ )
+ op.execute(f"CREATE SEQUENCE {get_inv()}.code_roles_seq;")
+
+
+def downgrade():
+ op.drop_table('code_roles', schema=f'{get_inv()}')
+ op.execute(f"DROP SEQUENCE {get_inv()}.code_roles_seq;")
diff --git a/ereuse_devicehub/modules/oidc/models.py b/ereuse_devicehub/modules/oidc/models.py
index 790e03a9..d5737c00 100644
--- a/ereuse_devicehub/modules/oidc/models.py
+++ b/ereuse_devicehub/modules/oidc/models.py
@@ -4,12 +4,17 @@ from authlib.integrations.sqla_oauth2 import (
OAuth2TokenMixin,
)
from flask import g
+from werkzeug.security import gen_salt
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.models import Thing
from ereuse_devicehub.resources.user.models import User
+def gen_code():
+ return gen_salt(24)
+
+
class MemberFederated(Thing):
__tablename__ = 'member_federated'
@@ -74,3 +79,11 @@ class OAuth2Token(Thing, OAuth2TokenMixin):
db.ForeignKey('member_federated.dlt_id_provider', ondelete='CASCADE'),
)
member = db.relationship('MemberFederated')
+
+
+class Code2Roles(Thing):
+ __tablename__ = 'code_roles'
+
+ id = db.Column(db.Integer, primary_key=True)
+ code = db.Column(db.String(40), default=gen_code, nullable=False)
+ roles = db.Column(db.String(40), unique=False, nullable=False)
diff --git a/ereuse_devicehub/modules/oidc/views.py b/ereuse_devicehub/modules/oidc/views.py
index 36f23c4b..26778761 100644
--- a/ereuse_devicehub/modules/oidc/views.py
+++ b/ereuse_devicehub/modules/oidc/views.py
@@ -14,16 +14,22 @@ from flask import (
request,
session,
url_for,
+ current_app as app
)
from flask_login import login_required
from ereuse_devicehub import __version__, messages
+from ereuse_devicehub.db import db
from ereuse_devicehub.modules.oidc.forms import (
AuthorizeForm,
CreateClientForm,
ListInventoryForm,
)
-from ereuse_devicehub.modules.oidc.models import MemberFederated, OAuth2Client
+from ereuse_devicehub.modules.oidc.models import (
+ MemberFederated,
+ OAuth2Client,
+ Code2Roles
+)
from ereuse_devicehub.modules.oidc.oauth2 import (
authorization,
generate_user_info,
@@ -124,20 +130,19 @@ class SelectInventoryView(GenericMixin):
title = "Select an Inventory"
def dispatch_request(self):
- form = ListInventoryForm()
- if form.validate_on_submit():
- return redirect(form.save(), code=302)
+ host = app.config.get('HOST', '').strip("/")
+ url = "https://ebsi-pcp-wallet-ui.vercel.app/oid4vp?"
+ url += f"client_id=https://{host}&"
+ url += "presentation_definition_uri=https%3A%2F%2Fiotaledger.github.io"
+ url += "%2Febsi-stardust-components%2Fpublic%2Fpresentation-definition-ex1.json&"
+ url += f"response_uri=https://{host}/allow_code_oidc4vp"
+ url += "&state=1700822573400&response_type=vp_token&response_mode=direct_post"
+ url += "&nonce=DybC3A%3D%3D"
next = request.args.get('next', '#')
- context = {
- 'next': next,
- 'form': form,
- 'title': self.title,
- 'user': g.user,
- 'grant': '',
- 'version': __version__,
- }
- return render_template(self.template_name, **context)
+ session['next_url'] = next
+
+ return redirect(url, code=302)
class AllowCodeView(GenericMixin):
@@ -222,95 +227,91 @@ class AllowCodeOidc4vpView(GenericMixin):
discovery = {}
def dispatch_request(self):
+ vcredential = self.get_credential()
+ if not vcredential:
+ return jsonify({"error": "No there are credentials"})
+
+ roles = self.verify(vcredential)
+ if not roles:
+ return jsonify({"error": "No there are roles"})
+
+ uri = self.get_response_uri(roles)
+
+ return jsonify({"redirect_uri": uri})
+
+ def get_credential(self):
self.vp_token = request.values.get("vp_token")
pv = self.vp_token.split(".")
token = json.loads(base64.b64decode(pv[1]).decode())
+ return token.get('vp', {}).get("verifiableCredential")
+
+ def verify(self, vcredential):
+ WALLET_INX_EBSI_PLUGIN_TOKEN = app.config.get(
+ 'WALLET_INX_EBSI_PLUGIN_TOKEN'
+ )
+ WALLET_INX_EBSI_PLUGIN_URL = app.config.get(
+ 'WALLET_INX_EBSI_PLUGIN_URL'
+ )
headers = {
'Content-Type': 'application/json',
- 'Authorization': f'Bearer WALLET_INX_EBSI_PLUGIN_TOKEN'
+ 'Authorization': f'Bearer {WALLET_INX_EBSI_PLUGIN_TOKEN}'
}
- vcredential = token.get('vp', {}).get("verifiableCredential")
- if not vcredential:
- return
-
data = json.dumps({
"type": "VerificationRequest",
"jwtCredential": vcredential
})
- result = requests.post(WALLET_INX_EBSI_PLUGIN_URL, headers=headers, data=data)
+ result = requests.post(
+ WALLET_INX_EBSI_PLUGIN_URL,
+ headers=headers,
+ data=data
+ )
+
if result.status_code != 200:
return
vps = json.loads(result.text)
if not vps.get('verified'):
return
- roles = vps['credential']['credentialSubject'].get('role')
- if not roles:
- return
- return jsonify({"result": "ok"})
- # if not self.code or not self.oidc:
- # return self.redirect()
+ return vps['credential']['credentialSubject'].get('role')
- # self.member = MemberFederated.query.filter(
- # MemberFederated.dlt_id_provider == self.oidc,
- # MemberFederated.client_id.isnot(None),
- # MemberFederated.client_secret.isnot(None),
- # ).first()
+ def get_response_uri(selfi, roles):
+ code = Code2Roles(roles=roles)
+ db.session.add(code)
+ db.session.commit()
- # if not self.member:
- # return self.redirect()
+ url = "https://{host}/allow_code_oidc4vp2?code={code}".format(
+ host=app.config.get('HOST'),
+ code=code.code
+ )
+ return url
- # self.get_token()
- # if 'error' in self.token:
- # messages.error(self.token.get('error', ''))
- # return self.redirect()
- # self.get_user_info()
- # return self.redirect()
+class AllowCodeOidc4vp2View(GenericMixin):
+ methods = ['GET', 'POST']
- def get_discovery(self):
- if self.discovery:
- return self.discovery
+ def dispatch_request(self):
+ self.code = request.args.get('code')
+ if not self.code:
+ return self.redirect()
- try:
- url_well_known = self.member.domain + '.well-known/openid-configuration'
- self.discovery = requests.get(url_well_known).json()
- except Exception:
- self.discovery = {'code': 404}
+ self.get_user_info()
- return self.discovery
-
- def get_token(self):
- data = {'grant_type': 'authorization_code', 'code': self.code}
- url = self.member.domain + '/oauth/token'
- url = self.get_discovery().get('token_endpoint', url)
-
- auth = (self.member.client_id, self.member.client_secret)
- msg = requests.post(url, data=data, auth=auth)
- self.token = json.loads(msg.text)
+ return self.redirect()
def redirect(self):
url = session.get('next_url') or '/login'
return redirect(url)
def get_user_info(self):
- if self.userinfo:
- return self.userinfo
- if 'access_token' not in self.token:
+ code = Code2Roles.query.filter(code=self.code).first()
+
+ if not code:
return
- url = self.member.domain + '/oauth/userinfo'
- url = self.get_discovery().get('userinfo_endpoint', url)
- access_token = self.token['access_token']
- token_type = self.token.get('token_type', 'Bearer')
- headers = {"Authorization": f"{token_type} {access_token}"}
-
- msg = requests.get(url, headers=headers)
- self.userinfo = json.loads(msg.text)
- rols = self.userinfo.get('rols', [])
- session['rols'] = [(k, k) for k in rols]
- return self.userinfo
+ session['rols'] = [(k.strip(), k.strip()) for k in code.roles.split(",")]
+ db.session.delete(code)
+ db.session.commit()
##########
@@ -320,6 +321,7 @@ oidc.add_url_rule('/create_client', view_func=CreateClientView.as_view('create_c
oidc.add_url_rule('/oauth/authorize', view_func=AuthorizeView.as_view('autorize_oidc'))
oidc.add_url_rule('/allow_code', view_func=AllowCodeView.as_view('allow_code'))
oidc.add_url_rule('/allow_code_oidc4vp', view_func=AllowCodeOidc4vpView.as_view('allow_code_oidc4vp'))
+oidc.add_url_rule('/allow_code_oidc4vp2', view_func=AllowCodeOidc4vp2View.as_view('allow_code_oidc4vp2'))
oidc.add_url_rule('/oauth/token', view_func=IssueTokenView.as_view('oauth_issue_token'))
oidc.add_url_rule(
'/oauth/userinfo', view_func=OauthProfileView.as_view('oauth_user_info')