Compare commits
10 Commits
f9ec594a0e
...
2283f20ab2
Author | SHA1 | Date |
---|---|---|
Cayo Puigdefabregas | 2283f20ab2 | |
Cayo Puigdefabregas | a4c7b2a744 | |
Cayo Puigdefabregas | da8d43f9f6 | |
Cayo Puigdefabregas | f0710e88ec | |
Cayo Puigdefabregas | 55839a26ea | |
Cayo Puigdefabregas | 39f0300a28 | |
Cayo Puigdefabregas | e5dbb09025 | |
Cayo Puigdefabregas | c948b0bca5 | |
Cayo Puigdefabregas | cd440b9931 | |
Cayo Puigdefabregas | 44b1a245b6 |
15
README.md
15
README.md
|
@ -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. The following steps describe how to run and use it.
|
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.
|
||||||
|
|
||||||
1. Download the sources:
|
1. Download the sources:
|
||||||
```
|
```
|
||||||
|
@ -20,7 +20,7 @@ There is a Docker compose file for an automated deployment. The following steps
|
||||||
cd devicehub-teal
|
cd devicehub-teal
|
||||||
```
|
```
|
||||||
|
|
||||||
2. If you want to initialise your DeviceHub instance with sample device snapshots, copy it/them into that directory. e.g.
|
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.
|
||||||
```
|
```
|
||||||
cp snapshot01.json examples/snapshots/
|
cp snapshot01.json examples/snapshots/
|
||||||
```
|
```
|
||||||
|
@ -30,7 +30,7 @@ There is a Docker compose file for an automated deployment. The following steps
|
||||||
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 your 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 one of the two 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,15 +45,12 @@ 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
|
||||||
```
|
```
|
||||||
You can see the [manual install step 9]('https://github.com/eReuse/devicehub-teal/blob/oidc4vp/README_MANUAL_INSTALLATION.md#installing') for more details.
|
The first six values should come from an already operational [API_DLT connector](https://gitlab.com/dsg-upc/ereuse-dpp) service instance.
|
||||||
|
|
||||||
|
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:
|
||||||
```
|
```
|
||||||
|
|
|
@ -110,6 +110,7 @@ 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)
|
||||||
|
|
|
@ -70,7 +70,10 @@ 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(self.password.data)
|
dlt_keys = user.get_dlt_keys(
|
||||||
|
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
|
||||||
|
|
|
@ -62,7 +62,9 @@ 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)
|
||||||
|
|
||||||
|
@ -87,7 +89,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:
|
if rols and rols != [('', '')]:
|
||||||
self.context['rols'] = rols
|
self.context['rols'] = rols
|
||||||
|
|
||||||
if 'dpp' not in app.blueprints.keys():
|
if 'dpp' not in app.blueprints.keys():
|
||||||
|
@ -96,10 +98,13 @@ 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'] = [(x.strip(), x.strip()) for x in role.split(",")]
|
self.context['rols'] = _role
|
||||||
|
return _role
|
||||||
|
|
||||||
def get_rol(self):
|
def get_rol(self):
|
||||||
rols = self.context.get('rols', [])
|
rols = self.context.get('rols', [])
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
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
|
||||||
|
@ -11,7 +13,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__()
|
||||||
|
@ -27,15 +29,13 @@ 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")
|
||||||
api_dlt = app.config.get('API_DLT')
|
ethereum = {"data": data.get("data")}
|
||||||
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,31 +43,20 @@ 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)
|
||||||
|
|
||||||
try:
|
roles = []
|
||||||
# TODO Not works
|
token_dlt = ethereum["data"]["api_token"]
|
||||||
with app.app_context():
|
api_dlt = app.config.get('API_DLT')
|
||||||
ses = g.get('session', None)
|
api = API(api_dlt, token_dlt, "ethereum")
|
||||||
ses["eth_pub_key"] = eth_pub_key
|
result = api.check_user_roles()
|
||||||
attributes = user.get_abac_attributes()
|
|
||||||
roles = attributes.get("role", ["Operator"])
|
if result.get('Status') == 200:
|
||||||
except Exception:
|
if 'Success' in result.get('Data', {}).get('status'):
|
||||||
roles = ["Operator"]
|
rols = result.get('Data', {}).get('data', {})
|
||||||
|
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)
|
||||||
|
|
|
@ -49,6 +49,30 @@ 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;")
|
||||||
|
|
|
@ -6,6 +6,7 @@ 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
|
||||||
|
@ -81,8 +82,8 @@ class OAuth2Token(Thing, OAuth2TokenMixin):
|
||||||
member = db.relationship('MemberFederated')
|
member = db.relationship('MemberFederated')
|
||||||
|
|
||||||
|
|
||||||
class Code2Roles(Thing):
|
class CodeRoles(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)
|
||||||
|
|
|
@ -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,
|
||||||
Code2Roles
|
CodeRoles
|
||||||
)
|
)
|
||||||
from ereuse_devicehub.modules.oidc.oauth2 import (
|
from ereuse_devicehub.modules.oidc.oauth2 import (
|
||||||
authorization,
|
authorization,
|
||||||
|
@ -132,16 +132,18 @@ class SelectInventoryView(GenericMixin):
|
||||||
|
|
||||||
def dispatch_request(self):
|
def dispatch_request(self):
|
||||||
host = app.config.get('HOST', '').strip("/")
|
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://iotaledger.github.io"
|
|
||||||
# url += "/ebsi-stardust-components/public/presentation-definition-ex1.json"
|
|
||||||
url += "/ebsi-stardust-components/public//presentation-definition-ereuse.json&"
|
|
||||||
url += f"response_uri=https://{host}/allow_code_oidc4vp"
|
|
||||||
url += "&state=1700822573400&response_type=vp_token&response_mode=direct_post"
|
|
||||||
url += "&nonce=DybC3A=="
|
|
||||||
|
|
||||||
next = request.args.get('next', '#')
|
next = request.args.get('next', '#')
|
||||||
|
# url = "https://ebsi-pcp-wallet-ui.vercel.app/oid4vp?"
|
||||||
|
# url += f"client_id=https://{host}&"
|
||||||
|
# url += "presentation_definition_uri=https://iotaledger.github.io"
|
||||||
|
# url += "/ebsi-stardust-components/public/presentation-definition-ex1.json"
|
||||||
|
# url += "/ebsi-stardust-components/public//presentation-definition-ereuse.json&"
|
||||||
|
# url += f"response_uri=https://{host}/allow_code_oidc4vp"
|
||||||
|
# url += "&state=1700822573400&response_type=vp_token&response_mode=direct_post"
|
||||||
|
url = app.config.get('VERIFY_URL')
|
||||||
|
url += f"?response_uri=http://{host}:5000/allow_code_oidc4vp"
|
||||||
|
url += '&presentation_definition=["EOperatorClaim"]'
|
||||||
|
|
||||||
session['next_url'] = next
|
session['next_url'] = next
|
||||||
|
|
||||||
return redirect(url, code=302)
|
return redirect(url, code=302)
|
||||||
|
@ -233,7 +235,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.verify(vcredential)
|
roles = self.get_roles(vcredential)
|
||||||
if not roles:
|
if not roles:
|
||||||
return jsonify({"error": "No there are roles"})
|
return jsonify({"error": "No there are roles"})
|
||||||
|
|
||||||
|
@ -242,55 +244,27 @@ class AllowCodeOidc4vpView(GenericMixin):
|
||||||
return jsonify({"redirect_uri": uri})
|
return jsonify({"redirect_uri": uri})
|
||||||
|
|
||||||
def get_credential(self):
|
def get_credential(self):
|
||||||
self.vp_token = request.values.get("vp_token")
|
pv = request.values.get("vp_token")
|
||||||
pv = self.vp_token.split(".")
|
self.code = request.values.get("code")
|
||||||
token = json.loads(base64.b64decode(pv[1]).decode())
|
token = json.loads(base64.b64decode(pv).decode())
|
||||||
return token.get('vp', {}).get("verifiableCredential")
|
return token.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 = Code2Roles(roles=roles)
|
code = CodeRoles(roles=roles)
|
||||||
db.session.add(code)
|
db.session.add(code)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
url = "https://{host}/allow_code_oidc4vp2?code={code}".format(
|
url = "http://{host}:5000/allow_code_oidc4vp2?code={code}".format(
|
||||||
host=app.config.get('HOST'),
|
host=app.config.get('HOST'),
|
||||||
code=code.code
|
code=code.code
|
||||||
)
|
)
|
||||||
|
@ -314,7 +288,7 @@ class AllowCodeOidc4vp2View(View):
|
||||||
return redirect(url)
|
return redirect(url)
|
||||||
|
|
||||||
def get_user_info(self):
|
def get_user_info(self):
|
||||||
code = Code2Roles.query.filter_by(code=self.code).first()
|
code = CodeRoles.query.filter_by(code=self.code).first()
|
||||||
|
|
||||||
if not code:
|
if not code:
|
||||||
return
|
return
|
||||||
|
|
Reference in New Issue