Compare commits

..

No commits in common. "2283f20ab21c652fc83186eea5d6a0a4452c4bdb" and "f9ec594a0ec8dfb639835bbff4e239b170400704" have entirely different histories.

8 changed files with 94 additions and 88 deletions

View file

@ -12,7 +12,7 @@ Devicehub relies on the existence of an [API_DLT connector](https://gitlab.com/d
Please visit the [Manual Installation](README_MANUAL_INSTALLATION.md) instructions to understand the detailed steps to install it locally or deploy it on a server. However, we recommend the following Docker deployment process. Please visit the [Manual Installation](README_MANUAL_INSTALLATION.md) instructions to understand the detailed steps to install it locally or deploy it on a server. However, we recommend the following Docker deployment process.
# Docker # Docker
There is a Docker compose file for an automated deployment. Two instances of DeviceHub will be deployed. The following steps describe how to run and use it. There is a Docker compose file for an automated deployment. The following steps describe how to run and use it.
1. Download the sources: 1. Download the sources:
``` ```
@ -20,7 +20,7 @@ There is a Docker compose file for an automated deployment. Two instances of Dev
cd devicehub-teal cd devicehub-teal
``` ```
2. If you want to initialise one of DeviceHub instances (running on port 5000) with sample device snapshots, copy it/them into that directory. e.g. 2. If you want to initialise your DeviceHub instance with sample device snapshots, copy it/them into that directory. e.g.
``` ```
cp snapshot01.json examples/snapshots/ cp snapshot01.json examples/snapshots/
``` ```
@ -30,7 +30,7 @@ There is a Docker compose file for an automated deployment. Two instances of Dev
IMPORT_SNAPSHOTS='n' IMPORT_SNAPSHOTS='n'
``` ```
To register new devices, the [workbench software](https://github.com/eReuse/workbench) can be run on a device to generate its hardware snapshot that can be uploaded to one of the two DeviceHub instance. To register new devices, the [workbench software](https://github.com/eReuse/workbench) can be run on a device to generate its hardware snapshot that can be uploaded to your DeviceHub instance.
3. Setup the environment variables in the .env file. You can find one example in examples/env.example. 3. Setup the environment variables in the .env file. You can find one example in examples/env.example.
If you don't have any, you can copy that example and modify the basic vars If you don't have any, you can copy that example and modify the basic vars
@ -45,12 +45,15 @@ You can use these parameters as default for a local test, but default values may
ABAC_TOKEN ABAC_TOKEN
ABAC_USER ABAC_USER
ABAC_URL ABAC_URL
```
These values should come from an already operational [API_DLT connector](https://gitlab.com/dsg-upc/ereuse-dpp) service instance.
If you want to use OIDC4VP, you need to set the vars:
```
SERVER_ID_FEDERATED SERVER_ID_FEDERATED
CLIENT_ID_FEDERATED CLIENT_ID_FEDERATED
``` ```
The first six values should come from an already operational [API_DLT connector](https://gitlab.com/dsg-upc/ereuse-dpp) service instance. You can see the [manual install step 9]('https://github.com/eReuse/devicehub-teal/blob/oidc4vp/README_MANUAL_INSTALLATION.md#installing') for more details.
For the last two values check [manual install step 9]('https://github.com/eReuse/devicehub-teal/blob/oidc4vp/README_MANUAL_INSTALLATION.md#installing') for more details.
4. Build and run the docker containers: 4. Build and run the docker containers:
``` ```

View file

@ -110,7 +110,6 @@ class DevicehubConfig(Config):
ABAC_TOKEN = config('ABAC_TOKEN', None) ABAC_TOKEN = config('ABAC_TOKEN', None)
ABAC_COOKIE = config('ABAC_COOKIE', None) ABAC_COOKIE = config('ABAC_COOKIE', None)
ABAC_URL = config('ABAC_URL', None) ABAC_URL = config('ABAC_URL', None)
VERIFY_URL = config('VERIFY_URL', None)
"""Definition of oauth jwt details.""" """Definition of oauth jwt details."""
OAUTH2_JWT_ENABLED = config('OAUTH2_JWT_ENABLED', False) OAUTH2_JWT_ENABLED = config('OAUTH2_JWT_ENABLED', False)

View file

@ -70,10 +70,7 @@ class LoginForm(FlaskForm):
self.form_errors.append(self.error_messages['inactive']) self.form_errors.append(self.error_messages['inactive'])
if 'dpp' in app.blueprints.keys(): if 'dpp' in app.blueprints.keys():
dlt_keys = user.get_dlt_keys( dlt_keys = user.get_dlt_keys(self.password.data)
self.password.data
).get('data', {})
token_dlt = dlt_keys.get('api_token') token_dlt = dlt_keys.get('api_token')
eth_pub_key = dlt_keys.get('eth_pub_key') eth_pub_key = dlt_keys.get('eth_pub_key')
session['token_dlt'] = token_dlt session['token_dlt'] = token_dlt

View file

@ -62,9 +62,7 @@ class DidView(View):
"isOperator": "operator.html", "isOperator": "operator.html",
"isVerifier": "verifier.html", "isVerifier": "verifier.html",
"operator": "operator.html", "operator": "operator.html",
"Operator": "operator.html",
"verifier": "verifier.html", "verifier": "verifier.html",
"Verifier": "verifier.html",
} }
self.template_name = tlmp.get(rol, self.template_name) self.template_name = tlmp.get(rol, self.template_name)
@ -89,7 +87,7 @@ class DidView(View):
if not g.user.is_authenticated and not rols: if not g.user.is_authenticated and not rols:
return [] return []
if rols and rols != [('', '')]: if rols:
self.context['rols'] = rols self.context['rols'] = rols
if 'dpp' not in app.blueprints.keys(): if 'dpp' not in app.blueprints.keys():
@ -98,13 +96,10 @@ class DidView(View):
if not session.get('token_dlt'): if not session.get('token_dlt'):
return [] return []
_role = g.user.get_rols_dlt()
role = session.get('iota_abac_attributes', {}).get('role', '') role = session.get('iota_abac_attributes', {}).get('role', '')
if not role:
if not _role:
return [] return []
self.context['rols'] = _role self.context['rols'] = [(x.strip(), x.strip()) for x in role.split(",")]
return _role
def get_rol(self): def get_rol(self):
rols = self.context.get('rols', []) rols = self.context.get('rols', [])

View file

@ -1,9 +1,7 @@
import json import json
import requests
import click import click
from ereuseapi.methods import API
from flask import g, current_app as app from flask import g, current_app as app
from ereuseapi.methods import register_user from ereuseapi.methods import register_user
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
@ -13,7 +11,7 @@ from ereuse_devicehub.modules.dpp.utils import encrypt
class RegisterUserDlt: class RegisterUserDlt:
# "operator", "verifier" or "witness" # "Operator", "Verifier" or "Witness"
def __init__(self, app) -> None: def __init__(self, app) -> None:
super().__init__() super().__init__()
@ -29,13 +27,15 @@ class RegisterUserDlt:
for d in dataset: for d in dataset:
self.add_user(d) self.add_user(d)
db.session.commit() db.session.commit()
def add_user(self, data): def add_user(self, data):
email = data.get("email") email = data.get("email")
name = email.split('@')[0] name = email.split('@')[0]
password = data.get("password") password = data.get("password")
ethereum = {"data": data.get("data")} api_dlt = app.config.get('API_DLT')
eth_priv_key = data.get("eth_priv_key")
eth_pub_key = data.get("eth_pub_key")
user = User.query.filter_by(email=email).first() user = User.query.filter_by(email=email).first()
@ -43,20 +43,31 @@ class RegisterUserDlt:
user = User(email=email, password=password) user = User(email=email, password=password)
user.individuals.add(Person(name=name)) user.individuals.add(Person(name=name))
try:
response = register_user(api_dlt, privateKey=eth_priv_key[2:])
api_token = response.get('data', {}).get('api_token')
except Exception:
api_token = ""
ethereum = {
"eth_pub_key": eth_pub_key,
"eth_priv_key": eth_priv_key,
"api_token": api_token
}
data_eth = json.dumps(ethereum) data_eth = json.dumps(ethereum)
user.api_keys_dlt = encrypt(password, data_eth) user.api_keys_dlt = encrypt(password, data_eth)
roles = [] try:
token_dlt = ethereum["data"]["api_token"] # TODO Not works
api_dlt = app.config.get('API_DLT') with app.app_context():
api = API(api_dlt, token_dlt, "ethereum") ses = g.get('session', None)
result = api.check_user_roles() ses["eth_pub_key"] = eth_pub_key
attributes = user.get_abac_attributes()
if result.get('Status') == 200: roles = attributes.get("role", ["Operator"])
if 'Success' in result.get('Data', {}).get('status'): except Exception:
rols = result.get('Data', {}).get('data', {}) roles = ["Operator"]
roles = [(k, k) for k, v in rols.items() if v]
user.rols_dlt = json.dumps(roles) user.rols_dlt = json.dumps(roles)
# if not user.id:
db.session.add(user) db.session.add(user)

View file

@ -49,30 +49,6 @@ def upgrade():
op.execute(f"CREATE SEQUENCE {get_inv()}.code_roles_seq;") op.execute(f"CREATE SEQUENCE {get_inv()}.code_roles_seq;")
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),
sa.PrimaryKeyConstraint('id')
)
op.execute(f"CREATE SEQUENCE code_roles_seq;")
def downgrade(): def downgrade():
op.drop_table('code_roles', schema=f'{get_inv()}') op.drop_table('code_roles', schema=f'{get_inv()}')
op.execute(f"DROP SEQUENCE {get_inv()}.code_roles_seq;") op.execute(f"DROP SEQUENCE {get_inv()}.code_roles_seq;")
op.drop_table('code_roles')
op.execute(f"DROP SEQUENCE code_roles_seq;")

View file

@ -6,7 +6,6 @@ from authlib.integrations.sqla_oauth2 import (
from flask import g from flask import g
from werkzeug.security import gen_salt from werkzeug.security import gen_salt
from flask import current_app
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.models import Thing from ereuse_devicehub.resources.models import Thing
from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.user.models import User
@ -82,8 +81,8 @@ class OAuth2Token(Thing, OAuth2TokenMixin):
member = db.relationship('MemberFederated') member = db.relationship('MemberFederated')
class CodeRoles(Thing): class Code2Roles(Thing):
# __tablename__ = 'code_roles' __tablename__ = 'code_roles'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(40), default=gen_code, nullable=False) code = db.Column(db.String(40), default=gen_code, nullable=False)

View file

@ -29,7 +29,7 @@ from ereuse_devicehub.modules.oidc.forms import (
from ereuse_devicehub.modules.oidc.models import ( from ereuse_devicehub.modules.oidc.models import (
MemberFederated, MemberFederated,
OAuth2Client, OAuth2Client,
CodeRoles Code2Roles
) )
from ereuse_devicehub.modules.oidc.oauth2 import ( from ereuse_devicehub.modules.oidc.oauth2 import (
authorization, authorization,
@ -132,18 +132,16 @@ class SelectInventoryView(GenericMixin):
def dispatch_request(self): def dispatch_request(self):
host = app.config.get('HOST', '').strip("/") host = app.config.get('HOST', '').strip("/")
next = request.args.get('next', '#') url = "https://ebsi-pcp-wallet-ui.vercel.app/oid4vp?"
# url = "https://ebsi-pcp-wallet-ui.vercel.app/oid4vp?" url += f"client_id=https://{host}&"
# url += f"client_id=https://{host}&" url += "presentation_definition_uri=https://iotaledger.github.io"
# url += "presentation_definition_uri=https://iotaledger.github.io"
# url += "/ebsi-stardust-components/public/presentation-definition-ex1.json" # url += "/ebsi-stardust-components/public/presentation-definition-ex1.json"
# url += "/ebsi-stardust-components/public//presentation-definition-ereuse.json&" url += "/ebsi-stardust-components/public//presentation-definition-ereuse.json&"
# url += f"response_uri=https://{host}/allow_code_oidc4vp" url += f"response_uri=https://{host}/allow_code_oidc4vp"
# url += "&state=1700822573400&response_type=vp_token&response_mode=direct_post" url += "&state=1700822573400&response_type=vp_token&response_mode=direct_post"
url = app.config.get('VERIFY_URL') url += "&nonce=DybC3A=="
url += f"?response_uri=http://{host}:5000/allow_code_oidc4vp"
url += '&presentation_definition=["EOperatorClaim"]'
next = request.args.get('next', '#')
session['next_url'] = next session['next_url'] = next
return redirect(url, code=302) return redirect(url, code=302)
@ -235,7 +233,7 @@ class AllowCodeOidc4vpView(GenericMixin):
if not vcredential: if not vcredential:
return jsonify({"error": "No there are credentials"}) return jsonify({"error": "No there are credentials"})
roles = self.get_roles(vcredential) roles = self.verify(vcredential)
if not roles: if not roles:
return jsonify({"error": "No there are roles"}) return jsonify({"error": "No there are roles"})
@ -244,27 +242,55 @@ class AllowCodeOidc4vpView(GenericMixin):
return jsonify({"redirect_uri": uri}) return jsonify({"redirect_uri": uri})
def get_credential(self): def get_credential(self):
pv = request.values.get("vp_token") self.vp_token = request.values.get("vp_token")
self.code = request.values.get("code") pv = self.vp_token.split(".")
token = json.loads(base64.b64decode(pv).decode()) token = json.loads(base64.b64decode(pv[1]).decode())
return token.get("verifiableCredential") 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}'
}
for v in vcredential:
data = json.dumps({
"type": "VerificationRequest",
"jwtCredential": v
})
result = requests.post(
WALLET_INX_EBSI_PLUGIN_URL,
headers=headers,
data=data
)
if result.status_code != 200:
return
vps = json.loads(result.text)
try:
roles = vps['credential']['credentialSubject'].get('role')
except Exception:
roles = None
if roles:
break
if not vps.get('verified'):
return
def get_roles(self, vps):
try:
for vp in vps:
roles = vp.get('credentialSubject', {}).get('role')
if roles:
return roles
except Exception:
roles = None
return roles return roles
def get_response_uri(selfi, roles): def get_response_uri(selfi, roles):
code = CodeRoles(roles=roles) code = Code2Roles(roles=roles)
db.session.add(code) db.session.add(code)
db.session.commit() db.session.commit()
url = "http://{host}:5000/allow_code_oidc4vp2?code={code}".format( url = "https://{host}/allow_code_oidc4vp2?code={code}".format(
host=app.config.get('HOST'), host=app.config.get('HOST'),
code=code.code code=code.code
) )
@ -288,7 +314,7 @@ class AllowCodeOidc4vp2View(View):
return redirect(url) return redirect(url)
def get_user_info(self): def get_user_info(self):
code = CodeRoles.query.filter_by(code=self.code).first() code = Code2Roles.query.filter_by(code=self.code).first()
if not code: if not code:
return return