Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
SMART
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
3
Issues
3
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
UNI-KLU
SMART
Commits
1988488c
Commit
1988488c
authored
Aug 05, 2020
by
Manuel
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feature/authentication' into develop
parents
e5f49a14
bfc3d44b
Changes
28
Hide whitespace changes
Inline
Side-by-side
Showing
28 changed files
with
890 additions
and
37 deletions
+890
-37
.gitignore
.gitignore
+6
-0
README.md
README.md
+3
-0
authentication.png
documentation/images/authentication.png
+0
-0
swagger.yml
...role-stage-discovery-microservice/app/configs/swagger.yml
+44
-18
main.py
src/data-hub/role-stage-discovery-microservice/app/main.py
+13
-2
requirements.txt
...ub/role-stage-discovery-microservice/app/requirements.txt
+2
-0
deployment.yml
...le-stage-discovery-microservice/deployment/deployment.yml
+8
-0
network_constants.py
src/modules/network_constants.py
+11
-3
__init__.py
src/modules/security/__init__.py
+0
-0
security.yml
src/modules/security/security.yml
+11
-0
security_util.py
src/modules/security/security_util.py
+93
-0
swagger_util.py
src/modules/security/swagger_util.py
+14
-0
swagger.yml
src/rest-gateway/app/configs/swagger.yml
+169
-5
__init__.py
src/rest-gateway/app/db/__init__.py
+0
-0
__init__.py
src/rest-gateway/app/db/entities/__init__.py
+0
-0
user.py
src/rest-gateway/app/db/entities/user.py
+45
-0
repository.py
src/rest-gateway/app/db/repository.py
+38
-0
main.py
src/rest-gateway/app/main.py
+17
-2
requirements.txt
src/rest-gateway/app/requirements.txt
+46
-4
blockchain_trace.py
src/rest-gateway/app/routes/blockchain_trace.py
+0
-0
debug.py
src/rest-gateway/app/routes/debug.py
+3
-0
user.py
src/rest-gateway/app/routes/user.py
+88
-0
login_wrapper.py
src/rest-gateway/app/services/login_wrapper.py
+14
-0
token_service.py
src/rest-gateway/app/services/token_service.py
+101
-0
user_service.py
src/rest-gateway/app/services/user_service.py
+101
-0
deployment.yml
src/rest-gateway/deployment/deployment.yml
+49
-1
Dockerfile
tools/welcome-page/Dockerfile
+5
-1
main.py
tools/welcome-page/main.py
+9
-1
No files found.
.gitignore
View file @
1988488c
...
...
@@ -4,3 +4,9 @@
*.log
**/env
**/venv
src/modules/certificate/articonf1.key
src/modules/certificate/articonf1.crt
src/modules/certificate/articonf1-chain.crt
README.md
View file @
1988488c
...
...
@@ -22,3 +22,6 @@ The scripts *build.py* and *deploy.py* are used to create Docker images and depl
1.
The semantic linking microservice receives the notification and GETs all traces (including the new one) from the trace retrieval microservice
1.
All traces can now be processed
![
Input handling image
](
documentation/images/input-handling.png
)
## API Authentication
![
Authentication diagram
](
documentation/images/authentication.png
)
\ No newline at end of file
documentation/images/authentication.png
0 → 100644
View file @
1988488c
53 KB
src/data-hub/role-stage-discovery-microservice/app/configs/swagger.yml
View file @
1988488c
...
...
@@ -4,6 +4,10 @@ info:
description
:
This is the documentation for the role stage discovery microservice.
version
:
"
1.0.0"
# Import security definitions from global security definition
securityDefinitions
:
$ref
:
'
../security/security.yml#securityDefinitions'
consumes
:
-
"
application/json"
produces
:
...
...
@@ -26,13 +30,15 @@ paths:
schema
:
type
:
object
responses
:
200
:
'
200'
:
description
:
"
Successful
echo
of
request
data"
#region Layers
/layers
:
post
:
operationId
:
"
routes.layers.post"
security
:
-
JwtRegular
:
[]
tags
:
-
"
Layers"
summary
:
"
Add
a
new
layer
[TODO:
or
overwrite
an
existing
one]"
...
...
@@ -44,18 +50,20 @@ paths:
schema
:
$ref
:
"
#/definitions/Layer-UpperCase"
responses
:
201
:
'
201'
:
description
:
"
Successful
operation"
400
:
'
400'
:
description
:
"
Invalid
input"
get
:
operationId
:
"
routes.layers.get"
security
:
-
JwtRegular
:
[]
tags
:
-
"
Layers"
summary
:
"
Get
all
layer
data"
parameters
:
[]
responses
:
200
:
'
200'
:
description
:
"
Successful
operation"
schema
:
$ref
:
"
#/definitions/LayerCollection"
...
...
@@ -63,6 +71,8 @@ paths:
/layers/{name}
:
get
:
operationId
:
"
routes.layers.get_by_name"
security
:
-
JwtRegular
:
[]
tags
:
-
"
Layers"
summary
:
"
Get
single
layer
data"
...
...
@@ -73,16 +83,18 @@ paths:
required
:
true
type
:
"
string"
responses
:
200
:
'
200'
:
description
:
"
Successful
operation"
schema
:
$ref
:
"
#/definitions/Layer"
404
:
'
404'
:
description
:
"
Layer
not
found"
/layers/{name}/nodes
:
get
:
operationId
:
"
routes.layers.get_nodes"
security
:
-
JwtRegular
:
[]
tags
:
-
"
Layers"
summary
:
"
Get
all
individual
nodes
for
the
layer"
...
...
@@ -93,14 +105,16 @@ paths:
required
:
true
type
:
"
string"
responses
:
200
:
'
200'
:
description
:
"
Successful
operation"
schema
:
$ref
:
"
#/definitions/NodeCollection"
404
:
'
404'
:
description
:
"
Layer
not
found"
post
:
operationId
:
"
routes.layers.post_nodes"
security
:
-
JwtRegular
:
[]
tags
:
-
"
Layers"
summary
:
"
Adds
a
single
or
multiple
nodes
to
the
layer"
...
...
@@ -117,14 +131,16 @@ paths:
schema
:
$ref
:
"
#/definitions/NodeCollection"
responses
:
201
:
'
201'
:
description
:
"
Successful
operation"
400
:
'
400'
:
description
:
"
Invalid
input"
/layers/{name}/clusters
:
get
:
operationId
:
"
routes.clustersets.get_by_name"
security
:
-
JwtRegular
:
[]
tags
:
-
"
Layers"
summary
:
"
Get
all
clusters
for
the
layer"
...
...
@@ -135,16 +151,18 @@ paths:
required
:
true
type
:
"
string"
responses
:
200
:
'
200'
:
description
:
"
Successful
operation"
schema
:
$ref
:
"
#/definitions/ClusterCollection"
404
:
'
404'
:
description
:
"
Layer
not
found"
/layers/{name}/timeslices
:
get
:
operationId
:
"
routes.timeslices.get_by_name"
security
:
-
JwtRegular
:
[]
tags
:
-
"
Layers"
summary
:
"
Get
all
timeslices
for
the
layer"
...
...
@@ -155,11 +173,11 @@ paths:
required
:
true
type
:
"
string"
responses
:
200
:
'
200'
:
description
:
"
Successful
operation"
schema
:
$ref
:
"
#/definitions/TimeSliceCollection"
404
:
'
404'
:
description
:
"
Layer
not
found"
#endregion
...
...
@@ -168,12 +186,14 @@ paths:
/rfc/run
:
post
:
operationId
:
"
routes.functions.run_agi_clustering_and_graph_creation"
security
:
-
JwtRegular
:
[]
tags
:
-
"
Remote
function
calls"
summary
:
"
Insert
locations
from
AGI,
create
clusters
for
starting
time
and
location
layers,
create
graphs
for
the
location
clusters"
parameters
:
[]
responses
:
204
:
'
204'
:
description
:
"
Successful
operation"
#endregion
...
...
@@ -182,12 +202,14 @@ paths:
/connectedClusters
:
get
:
operationId
:
"
routes.connClusters.get_conn_clusters"
security
:
-
JwtRegular
:
[]
tags
:
-
"
Connected"
summary
:
"
Get
connected
Clusters
data"
description
:
"
Returns
a
dictionary
of
cluster.
The
clusters
contain
the
associated
connected
clusters
and
connected
nodes
data."
responses
:
200
:
'
200'
:
description
:
"
Successful
operation"
schema
:
$ref
:
"
#/definitions/ConnectedDict"
...
...
@@ -195,6 +217,8 @@ paths:
/clusterSimilarity
:
get
:
operationId
:
"
routes.similarity.get_similarity"
security
:
-
JwtRegular
:
[]
tags
:
-
"
Similarity"
summary
:
"
Get
data
of
the
similarity
between
clusters."
...
...
@@ -212,7 +236,7 @@ paths:
description
:
"
Data
is
returned
in
batches
of
size
1000.
Returns
a
dictionary
where
the
key
is
a
tuple
of
cluster_labels
(i.e.
[0,319])
and
the
value
is
the
computed
similarity
between
2
clusters
in
the
tuple,
in
regard
to
each
layer
in
the
input.
\n
Note:
the
tuple
clusters
have
the
same
layer
and
the
computed
similarity
is
in
regard
to
clusters
from
OTHER
layers."
responses
:
200
:
'
200'
:
description
:
"
Successful
operation"
schema
:
$ref
:
"
#/definitions/ClusterSimilarityArray"
...
...
@@ -220,12 +244,14 @@ paths:
/clusterRunArray
:
get
:
operationId
:
"
routes.connRun.get_connected_run"
security
:
-
JwtRegular
:
[]
tags
:
-
"
RunId"
summary
:
"
Get
RunId"
description
:
"
Returns
the
RunId
and
the
associated
datetime
when
a
connection
of
clusters/simillarity
of
clusters
was
computed."
responses
:
200
:
'
200'
:
description
:
"
Successful
operation"
schema
:
$ref
:
"
#/definitions/ClusterRunArray"
...
...
src/data-hub/role-stage-discovery-microservice/app/main.py
View file @
1988488c
...
...
@@ -13,15 +13,26 @@ LOGGER = logging.getLogger(__name__)
#############################
import
connexion
from
security
import
swagger_util
from
pathlib
import
Path
# load swagger config
app
=
connexion
.
App
(
__name__
,
specification_dir
=
'configs/'
)
app
.
add_api
(
'swagger.yml'
)
app
.
add_api
(
swagger_util
.
get_bundled_specs
(
Path
(
"configs/swagger.yml"
)),
resolver
=
connexion
.
RestyResolver
(
"cms_rest_api"
))
@
app
.
route
(
'/'
,
methods
=
[
'GET'
])
def
api_root
():
return
'Endpoint of role-stage-discovery-microservice!'
# SSL configuration
try
:
# should be ../../../modules/certificate local
certificate_path
=
os
.
environ
[
'ARTICONF_CERTIFICATE_PATH'
]
except
KeyError
:
certificate_path
=
'/srv/articonf/'
context
=
(
os
.
path
.
normpath
(
f
'{certificate_path}/articonf1.crt'
),
os
.
path
.
normpath
(
f
'{certificate_path}/articonf1.key'
))
# certificate and key files
# start app
if
__name__
==
'__main__'
:
app
.
run
(
host
=
'0.0.0.0'
,
port
=
5000
,
debug
=
True
)
app
.
run
(
host
=
'0.0.0.0'
,
port
=
5000
,
ssl_context
=
context
)
src/data-hub/role-stage-discovery-microservice/app/requirements.txt
View file @
1988488c
...
...
@@ -27,6 +27,7 @@ mccabe==0.6.1
networkx==2.4
numpy==1.18.1
openapi-spec-validator==0.2.8
prance==0.19.0
pycodestyle==2.5.0
pylint==2.4.4
pymongo==3.10.1
...
...
@@ -38,6 +39,7 @@ requests==2.22.0
rope==0.16.0
scikit-learn==0.22.1
scipy==1.4.1
semver==2.10.2
six==1.14.0
swagger-ui-bundle==0.0.6
typed-ast==1.4.1
...
...
src/data-hub/role-stage-discovery-microservice/deployment/deployment.yml
View file @
1988488c
...
...
@@ -32,6 +32,14 @@ spec:
image
:
alexx882/role-stage-discovery-microservice
ports
:
-
containerPort
:
5000
volumeMounts
:
-
mountPath
:
/srv/articonf
name
:
articonf
volumes
:
-
name
:
articonf
hostPath
:
path
:
/srv/articonf
type
:
Directory
---
apiVersion
:
v1
kind
:
Service
...
...
src/modules/network_constants.py
View file @
1988488c
...
...
@@ -18,6 +18,14 @@ SEMANTIC_LINKING_DB_PORT = 27017
## Role Stage Discovery
ROLESTAGE_DISCOVERY_HOSTNAME
=
'role-stage-discovery'
ROLESTAGE_DISCOVERY_REST_PORT
=
30103
ROLESTAGE_DISCOVERY_DB_HOSTNAME
=
f
'articonf1.itec.aau.at'
ROLESTAGE_DISCOVERY_DB_PORT
=
30104
\ No newline at end of file
ROLESTAGE_DISCOVERY_REST_PORT
=
80
ROLESTAGE_DISCOVERY_DB_HOSTNAME
=
f
'{ROLESTAGE_DISCOVERY_HOSTNAME}-db'
ROLESTAGE_DISCOVERY_DB_PORT
=
27017
## Rest Gateway
# REST_GATEWAY_HOSTNAME = 'rest-gateway'
# REST_GATEWAY_DB_HOSTNAME = 'rest-gateway-db'
REST_GATEWAY_HOSTNAME
=
'rest-gateway'
REST_GATEWAY_REST_PORT
=
80
REST_GATEWAY_DB_HOSTNAME
=
f
'{REST_GATEWAY_HOSTNAME}-db'
REST_GATEWAY_DB_PORT
=
27017
src/modules/security/__init__.py
0 → 100644
View file @
1988488c
src/modules/security/security.yml
0 → 100644
View file @
1988488c
securityDefinitions
:
JwtRegular
:
type
:
apiKey
name
:
Authorization
in
:
header
x-apikeyInfoFunc
:
"
security.security_util.verifyTokenRegular"
JwtAdmin
:
type
:
apiKey
name
:
Authorization
in
:
header
x-apikeyInfoFunc
:
"
security.security_util.verifyTokenAdmin"
src/modules/security/security_util.py
0 → 100644
View file @
1988488c
# global import, red is normal don't worry
import
network_constants
import
requests
import
json
from
typing
import
Dict
,
List
class
TokenStash
:
'''
used to keep track of already verified tokens in order to mitigate the traffic
to the user-microservice
'''
trusted_tokens
=
{}
roles
=
{}
@
staticmethod
def
add
(
token
:
str
,
username
:
str
,
role
:
str
):
'''
adds a verified token to the stash
'''
TokenStash
.
trusted_tokens
[
token
]
=
username
TokenStash
.
roles
[
token
]
=
role
@
staticmethod
def
is_token_cached
(
token
:
str
)
->
str
:
'''
returns the associated username to a token, None otherwise
'''
if
token
in
TokenStash
.
trusted_tokens
and
token
in
TokenStash
.
roles
:
return
{
"sub"
:
TokenStash
.
trusted_tokens
[
token
],
"role"
:
TokenStash
.
roles
[
token
]}
return
None
def
decodeToken
(
token
:
str
,
roles
:
List
[
str
]
=
[])
->
Dict
:
'''
verifies the passed token on the user-microservice and returns a dictionary with the
subject entry if the verification was successful, an error is raised otherwise
@params:
token - Required : JWT token from authorization header, must start with "Bearer "
roles - Optional : User must have at least one of these roles
'''
if
not
token
.
startswith
(
"Bearer "
):
raise
ValueError
(
'Invalid JWT token (must be a Bearer string)'
)
token
=
token
[
7
:]
cached_data
=
TokenStash
.
is_token_cached
(
token
)
if
cached_data
!=
None
:
# Re-Use cached token
return
cached_data
url
=
f
'https://{network_constants.REST_GATEWAY_HOSTNAME}:{network_constants.REST_GATEWAY_REST_PORT}/api/tokens/{token}'
response
=
requests
.
post
(
url
,
verify
=
False
,
proxies
=
{
"http"
:
None
,
"https"
:
None
}
)
if
response
.
status_code
!=
200
:
raise
ValueError
(
f
"Validation of token failed ({response.status_code})!"
)
data
=
json
.
loads
(
response
.
text
)
if
not
"username"
in
data
or
not
"role"
in
data
:
raise
ValueError
(
f
"Validation of token failed (missing field in verification response)!"
)
if
len
(
roles
)
>
0
and
data
[
"role"
]
not
in
roles
:
raise
ValueError
(
f
"Validation of token failed (wrong role)!"
)
TokenStash
.
add
(
token
,
data
[
"username"
],
data
[
"role"
])
return
{
"sub"
:
data
[
"username"
],
"role"
:
data
[
"role"
]}
def
_verify
(
token
:
str
,
roles
:
List
[
str
]
=
[]):
try
:
token_info
=
decodeToken
(
token
,
roles
=
roles
)
return
token_info
except
Exception
as
e
:
print
(
"ERROR DURING TOKEN VALIDATION: "
+
str
(
e
))
return
None
def
verifyTokenRegular
(
token
,
required_scopes
):
return
_verify
(
token
)
def
verifyTokenAdmin
(
token
,
required_scopes
):
return
_verify
(
token
,
roles
=
[
"a"
])
\ No newline at end of file
src/modules/security/swagger_util.py
0 → 100644
View file @
1988488c
from
typing
import
Dict
,
Any
from
pathlib
import
Path
import
prance
def
get_bundled_specs
(
main_file
:
Path
)
->
Dict
[
str
,
Any
]:
'''
parses the given swagger.yml file and resolves dependencies
from that file to enable the possibility to split the
configuration into several files
'''
parser
=
prance
.
ResolvingParser
(
str
(
main_file
.
absolute
()),
lazy
=
True
,
backend
=
'openapi-spec-validator'
)
parser
.
parse
()
return
parser
.
specification
\ No newline at end of file
src/rest-gateway/app/configs/swagger.yml
View file @
1988488c
...
...
@@ -4,6 +4,10 @@ info:
description
:
This is the documentation for the RESTful API gateway.
version
:
"
1.0.0"
# Import security definitions from seperate file
securityDefinitions
:
$ref
:
'
../security/security.yml#securityDefinitions'
consumes
:
-
"
application/json"
produces
:
...
...
@@ -11,11 +15,127 @@ produces:
basePath
:
"
/api"
# Paths supported by the server application
paths
:
/secret
:
get
:
security
:
-
JwtRegular
:
[]
operationId
:
"
routes.user.secret"
tags
:
-
"
User"
summary
:
"
Testpage
for
authentication"
description
:
"
Should
only
be
accessible
with
a
valid
JWT
token
in
the
'authorization'
header"
responses
:
'
200'
:
description
:
"
OK"
'
401'
:
description
:
"
No
or
an
invalid
token
was
provided"
/tokens/{token}
:
post
:
operationId
:
"
routes.user.verify"
tags
:
-
"
User"
summary
:
"
Verifies
a
user
token"
description
:
"
Verifies
a
user
token"
parameters
:
-
name
:
"
token"
in
:
"
path"
description
:
"
Target
token
that
will
be
verified"
required
:
true
type
:
"
string"
responses
:
'
200'
:
description
:
"
Verification
successful"
'
401'
:
description
:
"
Invalid
token"
/tokens
:
post
:
operationId
:
"
routes.user.authenticate"
tags
:
-
"
User"
summary
:
"
Authenticates
user
at
the
backend"
description
:
"
Authenticates
user
at
the
backend
creating
a
JWT
token
in
the
backend"
parameters
:
-
in
:
body
name
:
"
Object"
required
:
true
schema
:
$ref
:
'
#/definitions/TokenRequest'
responses
:
'
200'
:
description
:
"
Authentication
successful"
schema
:
$ref
:
"
#/definitions/TokenReply"
'
400'
:
description
:
"
Wrong
credentials"
/users/username/{username}
:
delete
:
security
:
-
JwtAdmin
:
[]
operationId
:
"
routes.user.delete"
tags
:
-
"
User"
summary
:
"
Deletes
a
user
identified
by
the
username
from
the
database"
description
:
"
Deletes
a
user
identified
by
the
username
from
the
database"
parameters
:
-
name
:
"
username"
in
:
"
path"
description
:
"
Username
of
the
user
to
be
deleted"
required
:
true
type
:
"
string"
responses
:
'
200'
:
description
:
"
Deletion
succeeded"
'
400'
:
description
:
"
User
does
not
exist"
/users
:
get
:
security
:
-
JwtAdmin
:
[]
operationId
:
"
routes.user.all"
tags
:
-
"
User"
summary
:
"
Retrieves
all
users
from
the
database"
description
:
"
Retrieves
all
users
from
the
database"
responses
:
'
200'
:
description
:
complete user object including numeric ID
schema
:
type
:
array
items
:
$ref
:
"
#/definitions/User"
'
400'
:
description
:
wrong username or password
post
:
operationId
:
"
routes.user.add"
tags
:
-
"
User"
summary
:
"
Adds
a
new
user
to
the
database"
description
:
"
Adds
a
new
user
to
the
database"
parameters
:
-
in
:
body
name
:
"
Object"
required
:
true
schema
:
type
:
object
properties
:
username
:
type
:
string
example
:
"
username@domain.com"
password
:
type
:
string
example
:
"
secure_passw0rd"
responses
:
'
200'
:
description
:
"
User
was
added
to
the
database"
'
400'
:
description
:
"
User
already
exists"
/debug
:
post
:
operationId
:
"
r
est
.debug.echo"
operationId
:
"
r
outes
.debug.echo"
tags
:
-
"
Echo"
summary
:
"
Echo
function
for
debugging
purposes"
...
...
@@ -27,12 +147,12 @@ paths:
schema
:
type
:
object
responses
:
200
:
'
200'
:
description
:
"
Successful
echo
of
request
data"
/trace
:
post
:
operationId
:
"
r
est
.blockchain_trace.receive"
operationId
:
"
r
outes
.blockchain_trace.receive"
tags
:
-
"
Blockchain
Trace"
summary
:
"
Add
a
new
blockchain
trace
to
SMART"
...
...
@@ -45,12 +165,56 @@ paths:
schema
:
$ref
:
"
#/definitions/BlockchainTrace"
responses
:
201
:
'
201'
:
description
:
"
Successfully
added"
400
:
'
400'
:
description
:
"
Invalid
input"
definitions
:
TokenReply
:
type
:
"
object"
required
:
-
token
properties
:
token
:
type
:
string
example
:
"
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXJuYW1lQGRvbWFpbi5jb20iLCJjcmVhdGVkX2F0IjoiMjAyMC0wNy0xNSAxNTo0Mzo0OC43MjQ4MjciLCJ2YWxpZF91bnRpbCI6IjIwMjAtMDctMTYgMTU6NDM6NDguNzI0ODI3In0.aR2Xe3pXj_MBS9UJKqhiq4u9M6Bv41ILPaKpA8BVzIY"
TokenRequest
:
type
:
"
object"
required
:
-
username
-
password
properties
:
username
:
type
:
string
example
:
"
username@domain.com"
password
:
type
:
string
example
:
"
secure_passw0rd"
User
:
type
:
"
object"
required
:
-
username
-
password
-
role
-
created_at
-
last_login
properties
:
username
:
type
:
string
example
:
"
username@domain.com"
password
:
type
:
string
example
:
"
secure_passw0rd"
role
:
type
:
string
example
:
"
u"
created_at
:
type
:
string
example
:
"
2020-07-14
14:37:31.670671"
last_login
:
type
:
string
example
:
"
2020-07-14
14:37:31.670671"
BlockchainTrace
:
type
:
"
object"
properties
:
...
...
src/rest-gateway/app/db/__init__.py
0 → 100644
View file @
1988488c
src/rest-gateway/app/db/entities/__init__.py
0 → 100644
View file @
1988488c
src/rest-gateway/app/db/entities/user.py
0 → 100644
View file @
1988488c
import
json
from
typing
import
Dict
from
datetime
import
datetime
class
User
:
'''
This class represents a user in the SMART system
'''
def
__init__
(
self
,
username
:
str
,
password
:
str
,
role
:
str
=
"u"
):
'''
Initializes the new user object with the given data
@params:
username - Required : unique identifier for the user i.e. an E-Mail address
password - Required : raw, unhashed password used to authenticate the user later
role - Optional : indicates the privileges of the user "u": standard user
'''
self
.
username
=
username
self
.
password
=
password
self
.
created_at
=
str
(
datetime
.
now
())
self
.
last_login
=
str
(
datetime
.
now
())
self
.
role
=
role
def
to_serializable_dict
(
self
)
->
Dict
:
return
{
"username"
:
self
.
username
,
"password"
:
self
.
password
,
"role"
:
self
.
role
,
"created_at"
:
self
.
created_at
,
"last_login"
:
self
.
last_login
,
}
@
staticmethod
def
from_serializable_dict
(
user_dict
:
Dict
):
result
=
User
(
user_dict
[
"username"
],
user_dict
[
"password"
],
user_dict
[
"role"
])
result
.
created_at
=
user_dict
[
"created_at"
]
result
.
last_login
=
user_dict
[
"last_login"
]
return
result
def
__repr__
(
self
):
return
json
.
dumps
(
self
.
to_serializable_dict
())
def
__str__
(
self
):
return
f
"User({self.__repr__()})"
src/rest-gateway/app/db/repository.py
0 → 100644
View file @
1988488c
# global imports (dont't worry, red is normal)
import
network_constants
as
netconst
from
database.MongoRepositoryBase
import
MongoRepositoryBase
import
pymongo
import
json
from
db.entities.user
import
User
from
typing
import
List
class
Repository
(
MongoRepositoryBase
):
'''This is a repository for MongoDb.'''
def
__init__
(
self
):
super
()
.
__init__
(
netconst
.
REST_GATEWAY_DB_HOSTNAME
,
netconst
.
REST_GATEWAY_DB_PORT
,
'rest-gateway-db'
)
self
.
_user_collection
=
'user'
def
one_by_username
(
self
,
username
:
str
)
->
User
:
return
list
(
super
()
.
get_entries
(
self
.
_user_collection
,
selection
=
{
"username"
:
username
}))
def
add
(
self
,
user
:
User
):
super
()
.
insert_entry
(
self
.
_user_collection
,
user
.
to_serializable_dict
())
def
update
(
self
,
user
:
User
):
collection
=
self
.
_database
[
self
.
_user_collection
]
collection
.
update_one
({
"username"
:
user
.
username
},
{
"$set"
:
user
.
to_serializable_dict
()})
def
delete_all_with_username
(
self
,
username
:
str
):
collection
=
self
.
_database
[
self
.
_user_collection
]
collection
.
delete_many
({
"username"
:
username
})
# TODO maybe movable to MongoRepositoryBase?
def
all
(
self
)
->
List
[
User
]:
result
=
super
()
.
get_entries
(
self
.
_user_collection
,
projection
=
{
'_id'
:
False
})
return
list
(
result
)
\ No newline at end of file
src/rest-gateway/app/main.py
View file @
1988488c
# add modules folder to interpreter path
import
sys
import
os
import
prance
from
pathlib
import
Path
from
typing
import
Dict
,
Any
modules_path
=
'../../modules/'
if
os
.
path
.
exists
(
modules_path
):
sys
.
path
.
insert
(
1
,
modules_path
)
...
...
@@ -13,15 +17,26 @@ LOGGER = logging.getLogger(__name__)
#################################
import
connexion
from
security
import
swagger_util
# load swagger config
app
=
connexion
.
App
(
__name__
,
specification_dir
=
'configs/'
)
app
.
add_api
(
'swagger.yml'
)
app
.
add_api
(
swagger_util
.
get_bundled_specs
(
Path
(
"configs/swagger.yml"
)),
resolver
=
connexion
.
RestyResolver
(
"cms_rest_api"
))
@
app
.
route
(
'/'
,
methods
=
[
'GET'
])
def
api_root
():
return
'Endpoint of SMART RESTful API Gateway!'
# SSL configuration
try
:
certificate_path
=
os
.
environ
[
'ARTICONF_CERTIFICATE_PATH'
]
except
KeyError
:
certificate_path
=
'/srv/articonf/'
context
=
(
os
.
path
.
normpath
(
f
'{certificate_path}/articonf1.crt'
),
os
.
path
.
normpath
(
f
'{certificate_path}/articonf1.key'
))
# certificate and key files
# start app
if
__name__
==
'__main__'
:
app
.
run
(
host
=
'0.0.0.0'
,
port
=
5000
,
debug
=
True
,
use_reloader
=
False
)
# disable reloader so only subscribed once to rabbitmq
\ No newline at end of file
app
.
run
(
host
=
'0.0.0.0'
,
port
=
5000
,
debug
=
True
,
use_reloader
=
False
,
ssl_context
=
context
)
# disable reloader so only subscribed once to rabbitmq
\ No newline at end of file
src/rest-gateway/app/requirements.txt
View file @
1988488c
flask
connexion[swagger-ui]
pika
deprecated
astroid==2.4.2
attrs==19.3.0
autopep8==1.5.3
bcrypt==3.1.7
certifi==2020.6.20
cffi==1.14.0
chardet==3.0.4
click==7.1.2
clickclick==1.2.2
colorama==0.4.3
connexion==2.7.0
cryptography==2.9.2
Deprecated==1.2.10
Flask==1.1.2
idna==2.10
importlib-metadata==1.7.0
inflection==0.5.0
isort==4.3.21
itsdangerous==1.1.0
Jinja2==2.11.2
jsonschema==3.2.0
lazy-object-proxy==1.4.3
MarkupSafe==1.1.1
mccabe==0.6.1
openapi-spec-validator==0.2.8
pika==1.1.0
prance==0.19.0
pycodestyle==2.6.0
pycparser==2.20
PyJWT==1.7.1
pylint==2.5.3
pymongo==3.10.1
pyOpenSSL==19.1.0
pyrsistent==0.16.0
PyYAML==5.3.1
requests==2.24.0
rope==0.17.0
semver==2.10.2
six==1.15.0
swagger-ui-bundle==0.0.6
toml==0.10.1
typed-ast==1.4.1
urllib3==1.25.9
Werkzeug==1.0.1
wrapt==1.12.1
zipp==3.1.0
src/rest-gateway/app/r
est
/blockchain_trace.py
→
src/rest-gateway/app/r
outes
/blockchain_trace.py
View file @
1988488c
File moved
src/rest-gateway/app/r
est
/debug.py
→
src/rest-gateway/app/r
outes
/debug.py
View file @
1988488c
...
...
@@ -2,3 +2,6 @@ from flask import request
def
echo
():
return
request
.
json
def
test
():
return
"Hello there!"
\ No newline at end of file
src/rest-gateway/app/routes/user.py
0 → 100644
View file @
1988488c
# global imports (dont't worry, red is normal)
from
db.entities.user
import
User
from
services.user_service
import
UserService
from
services.login_wrapper
import
login_required
from
services.token_service
import
TokenService
from
flask
import
request
,
Response
import
bcrypt
import
jwt
import
json
def
secret
():
return
"Pineapple does not belong to pizza!"
def
verify
(
token
):
'''
verifies the validity of a JWT token.
performs the following tests (int this order):
- is the JWT token parsable? (it has not been damaged + the signature is valid)
- does the payload contain all necessary fields?
- does the user specified by the payload exist?
- is the expiration/creation date sound?
'''
try
:
user
=
TokenService
.
verify
(
"Bearer "
+
token
)
return
Response
(
status
=
200
,
response
=
json
.
dumps
(
user
.
to_serializable_dict
()))
except
ValueError
as
e
:
return
Response
(
status
=
401
,
response
=
str
(
e
))
def
authenticate
():
'''
takes the credentials from the user and generates a JWT token out of them
'''
data
=
request
.
json
username
=
data
[
"username"
]
try
:
user
=
UserService
.
get_by_credentials
(
username
,
data
[
"password"
])
TokenService
.
successful_authentication
(
user
)
return
{
"token"
:
TokenService
.
generate_token
(
user
)}
except
ValueError
as
e
:
# return 400 if the user does not exist or the password is wrong
return
Response
(
status
=
400
,
response
=
str
(
e
))
def
delete
(
username
):
'''
deletes a user from the DB. should be protected later
'''
try
:
UserService
.
delete
(
username
)
return
Response
(
status
=
200
)
except
ValueError
as
e
:
# return 400 if the user already exists
return
Response
(
status
=
400
,
response
=
str
(
e
))
def
add
():
'''
adds a new user to the DB. expects the provided password to be plaintext i.e. it should not
be encrypted, encoded or hashed
'''
data
=
request
.
json
# overwrite possibly existing role with "regular user"
data
[
"role"
]
=
"u"
username
=
data
[
"username"
]
try
:
UserService
.
add
(
username
,
data
[
"password"
],
data
[
"role"
])
except
ValueError
as
e
:
# return 400 if the user already exists
return
Response
(
status
=
400
,
response
=
str
(
e
))
return
Response
(
status
=
200
)
def
all
():
'''
return all users stored in the DB
'''
users
=
UserService
.
_repository
.
all
()
return
str
(
users
)
\ No newline at end of file
src/rest-gateway/app/services/login_wrapper.py
0 → 100644
View file @
1988488c
from
functools
import
wraps
from
flask
import
g
,
request
,
redirect
,
url_for
def
login_required
(
f
):
@
wraps
(
f
)
def
decorated_function
(
*
args
,
**
kwargs
):
auth
=
request
.
authorization
if
auth
==
None
:
return
redirect
(
url_for
(
'/api.rest_user_forbidden'
,
next
=
request
.
url
))
return
f
(
*
args
,
**
kwargs
)
return
decorated_function
src/rest-gateway/app/services/token_service.py
0 → 100644
View file @
1988488c
# global imports (dont't worry, red is normal)
from
db.entities.user
import
User
from
services.user_service
import
UserService
import
jwt
from
datetime
import
datetime
,
timedelta
from
typing
import
Dict
SIGNING_KEY
=
"yteNrMy6142WKwp8fKfrHkS5nlFpxtHgOXJh1ZPsOrV_gTcsO9eMY7aB7HUzRbTRO9dmZhCl3FdPtuvMe3K8aBA_wc2MmHRo8IkUIGmvUJGsAxKFClN_6oNW5fEvoeVKiL1krA-qjWbR_em-WksePgPoTsySW7QbKdi4f7cwuyK2_JZ2fQj9hDKlfJ2GzMXkKiWcfyCTr30yC6BviAFeRDD_Bpvg6znsrXr53Tq66hnwDwQ6QU7aHVu-bERblKZTYuvkSxsov6yRMEVWQoiuBITsQtIOcgSWK4Dy3BjSbqoIcKw3WG-s3wx1lTen19QbEu8vJC64e0iGeGDWT6vbtg"
TOKEN_VALIDITY_IN_DAYS
=
1
def
verifyTokenRegular
(
token
,
required_scopes
)
->
Dict
:
try
:
user
=
TokenService
.
verify
(
token
)
except
ValueError
as
e
:
print
(
f
'ERROR DURING TOKEN VALIDATION: {str(e)}'
)
return
None
if
not
user
.
role
in
UserService
.
_valid_roles
:
return
None
TokenService
.
successful_authentication
(
user
)
return
{
"sub"
:
user
.
username
}
def
verifyTokenAdmin
(
token
,
required_scopes
)
->
Dict
:
try
:
user
=
TokenService
.
verify
(
token
)
except
ValueError
as
e
:
print
(
f
'ERROR DURING TOKEN VALIDATION: {str(e)}'
)
return
None
if
not
user
.
role
==
"a"
:
print
(
f
"Required Role:'a', Provided Role: '{user.role}'"
)
return
None
TokenService
.
successful_authentication
(
user
)
return
{
"sub"
:
user
.
username
}
class
TokenService
:
@
staticmethod
def
successful_authentication
(
user
:
User
):
user
.
last_login
=
str
(
datetime
.
now
())
UserService
.
update
(
user
)
@
staticmethod
def
generate_token
(
user
:
User
)
->
str
:
'''
creates a JWT token for a user which has the following fields:
- username
- created_at
- valid_until
'''
created_at
=
datetime
.
now
()
valid_until
=
created_at
+
timedelta
(
days
=
1
)
return
jwt
.
encode
(
{
'username'
:
user
.
username
,
'created_at'
:
str
(
created_at
),
'valid_until'
:
str
(
valid_until
),
},
SIGNING_KEY
,
algorithm
=
'HS256'
)
.
decode
(
"utf-8"
)
@
staticmethod
def
verify
(
token
:
str
,
**
kwargs
)
->
User
:
'''
verifies the validity of a JWT token. Raises a ValueError if one of the tests failes
performs the following tests (int this order):
- is the JWT token parsable? (it has not been damaged + the signature is valid)
- does the payload contain all necessary fields?
- does the user specified by the payload exist?
- is the expiration/creation date sound?
@params:
token - Required : JWT token from authorization header, must start with "Bearer "
'''
if
not
token
.
startswith
(
"Bearer "
):
raise
ValueError
(
'Invalid JWT token (must be a Bearer string)'
)
token
=
token
[
7
:]
try
:
payload
=
jwt
.
decode
(
token
,
SIGNING_KEY
,
algorithms
=
[
'HS256'
])
except
:
raise
ValueError
(
'Invalid JWT token (decoding failed)'
)
# check if all needed fields are in the payload
if
not
"username"
in
payload
or
not
"created_at"
in
payload
or
not
"valid_until"
in
payload
:
return
'Invalid JWT token (missing fields)'
user
=
UserService
.
get_by_username
(
payload
[
"username"
])
# check if token has already expired
token_created_at
=
datetime
.
strptime
(
payload
[
"created_at"
],
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S.
%
f'
)
valid_until
=
datetime
.
strptime
(
payload
[
"valid_until"
],
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S.
%
f'
)
now
=
datetime
.
now
()
if
now
<=
token_created_at
or
now
>=
valid_until
:
raise
ValueError
(
'Invalid JWT token (token expired)'
)
return
user
\ No newline at end of file
src/rest-gateway/app/services/user_service.py
0 → 100644
View file @
1988488c
# global imports (dont't worry, red is normal)
from
db.repository
import
Repository
from
db.entities.user
import
User
from
datetime
import
datetime
import
bcrypt
class
UserService
:
_repository
=
Repository
()
# u ... regular user
# a ... admin user
_valid_roles
=
[
"u"
,
"a"
]
@
staticmethod
def
update
(
user
:
User
):
UserService
.
_repository
.
update
(
user
)
@
staticmethod
def
get_by_username
(
username
:
str
):
'''
fetches the given user from the database
throws a ValueError if the user does not exist
@params:
username - Required : string identifier for the user i.e. an email address
'''
user
=
UserService
.
_repository
.
one_by_username
(
username
)
# return 400 if the user does not exist
if
len
(
user
)
==
0
:
raise
ValueError
(
f
'User with username "{username}" does not exist'
)
return
User
.
from_serializable_dict
(
user
[
0
])
@
staticmethod
def
get_by_credentials
(
username
,
password
):
'''
fetches the given user from the database and checks if the password matches the stored one
throws a ValueError if the user does not exist or the password is wrong
@params:
username - Required : string identifier for the user i.e. an email address
password - Required : passphrase used to authenticate later, raw plaintext
'''
user
=
UserService
.
get_by_username
(
username
)
hashed_password
=
user
.
password
if
not
bcrypt
.
checkpw
(
password
.
encode
(
"utf-8"
),
hashed_password
.
encode
(
"utf-8"
)):
raise
ValueError
(
f
'Wrong credentials for user "{username}"'
)
return
user
@
staticmethod
def
delete
(
username
):
'''
deletes the given user from the database
throws a ValueError if the user does not exist
@params:
username - Required : string identifier for the user i.e. an email address
'''
reference_users
=
UserService
.
_repository
.
one_by_username
(
username
)
# return 400 if the user does not exist
if
len
(
reference_users
)
==
0
:
raise
ValueError
(
f
'User with username "{username}" does not exist'
)
UserService
.
_repository
.
delete_all_with_username
(
username
)
@
staticmethod
def
add
(
username
,
password
,
role
=
"u"
):
'''
adds the given user to the database
throws a ValueError if the user already exists
@params:
username - Required : string identifier for the user i.e. an email address
password - Required : passphrase used to authenticate later, raw plaintext
role - Optional : user type, one of the following: [u=regular user (default)]
'''
if
role
not
in
UserService
.
_valid_roles
:
raise
ValueError
(
f
'Role "{role}" is invalid. Must be one of {UserService._valid_roles}'
)
reference_users
=
UserService
.
_repository
.
one_by_username
(
username
)
if
len
(
reference_users
)
>
0
:
raise
ValueError
(
f
'User with username "{username}" already exists'
)
created_at
=
str
(
datetime
.
now
())
last_login
=
str
(
datetime
.
min
)
# hash the password using the BCrypt algorithm, which generates a string that
# contains the algorithm, the salt and the hash
password
=
bcrypt
.
hashpw
(
password
.
encode
(
"utf-8"
),
bcrypt
.
gensalt
())
.
decode
(
"utf-8"
)
user_new
=
User
(
username
,
password
,
role
=
role
)
user_new
.
created_at
=
created_at
user_new
.
last_login
=
last_login
UserService
.
_repository
.
add
(
user_new
)
\ No newline at end of file
src/rest-gateway/deployment/deployment.yml
View file @
1988488c
...
...
@@ -31,4 +31,52 @@ spec:
-
name
:
rest-gateway
image
:
alexx882/rest-gateway
ports
:
-
containerPort
:
5000
\ No newline at end of file
-
containerPort
:
5000
volumeMounts
:
-
mountPath
:
/srv/articonf
name
:
articonf
volumes
:
-
name
:
articonf
hostPath
:
path
:
/srv/articonf
type
:
Directory
---
apiVersion
:
v1
kind
:
Service
metadata
:
name
:
rest-gateway-db
spec
:
type
:
LoadBalancer
selector
:
app
:
rest-gateway-db
ports
:
-
name
:
http
port
:
27017
targetPort
:
27017
nodePort
:
30402
protocol
:
TCP
---
apiVersion
:
apps/v1
kind
:
Deployment
metadata
:
name
:
rest-gateway-db
spec
:
replicas
:
1
selector
:
matchLabels
:
app
:
rest-gateway-db
template
:
metadata
:
labels
:
app
:
rest-gateway-db
spec
:
containers
:
-
name
:
rest-gateway-db
image
:
mongo
env
:
-
name
:
MONGO_INITDB_ROOT_USERNAME
value
:
root
-
name
:
MONGO_INITDB_ROOT_PASSWORD
value
:
root
ports
:
-
containerPort
:
27017
tools/welcome-page/Dockerfile
View file @
1988488c
...
...
@@ -17,4 +17,8 @@ COPY main.py /app/
COPY
templates/ /app/templates/
RUN
chmod
a+x main.py
CMD
["python", "./main.py"]
\ No newline at end of file
CMD
["python", "./main.py"]
# docker build -t alexx882/hello-articonf .
# docker run --name articonf-home -p 80:5000 -v /srv/articonf:/srv/articonf -d alexx882/hello-articonf
# docker run --name articonf-home-ssl -p 443:5000 -v /srv/articonf:/srv/articonf -d alexx882/hello-articonf
\ No newline at end of file
tools/welcome-page/main.py
View file @
1988488c
import
os
from
flask
import
Flask
,
render_template
app
=
Flask
(
__name__
)
...
...
@@ -6,4 +7,11 @@ app = Flask(__name__)
def
hello_world
():
return
render_template
(
'index.html'
)
app
.
run
(
host
=
'0.0.0.0'
,
port
=
5000
,
debug
=
True
)
\ No newline at end of file
# SSL configuration
try
:
certificate_path
=
os
.
environ
[
'ARTICONF_CERTIFICATE_PATH'
]
except
KeyError
:
certificate_path
=
'/srv/articonf/'
context
=
(
os
.
path
.
normpath
(
f
'{certificate_path}/articonf1.crt'
),
os
.
path
.
normpath
(
f
'{certificate_path}/articonf1.key'
))
# certificate and key files
app
.
run
(
host
=
'0.0.0.0'
,
port
=
5000
,
debug
=
False
,
ssl_context
=
context
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment