Compare commits
No commits in common. "2283f20ab21c652fc83186eea5d6a0a4452c4bdb" and "f9ec594a0ec8dfb639835bbff4e239b170400704" have entirely different histories.
2283f20ab2
...
f9ec594a0e
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. 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:
|
||||||
```
|
```
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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', [])
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;")
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Reference in a new issue