Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
feudalBackend
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
16
Issues
16
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
feudal
feudalBackend
Commits
d45e4e76
Commit
d45e4e76
authored
Jun 12, 2018
by
Lukas Burgey
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement backchannel from the client to the backend
parent
0cc4ef92
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
128 additions
and
38 deletions
+128
-38
django_backend/backend/auth/v1/models.py
django_backend/backend/auth/v1/models.py
+28
-16
django_backend/backend/auth/v1/views.py
django_backend/backend/auth/v1/views.py
+12
-6
django_backend/backend/clientapi/urls.py
django_backend/backend/clientapi/urls.py
+1
-0
django_backend/backend/clientapi/views.py
django_backend/backend/clientapi/views.py
+33
-2
django_backend/backend/frontend/serializers.py
django_backend/backend/frontend/serializers.py
+1
-0
django_backend/backend/models.py
django_backend/backend/models.py
+53
-14
No files found.
django_backend/backend/auth/v1/models.py
View file @
d45e4e76
...
...
@@ -55,7 +55,7 @@ class OIDCConfig(db_models.Model):
args
=
{
'client_id'
:
self
.
client_id
,
'response_type'
:
'code'
,
'scope'
:
[
'openid'
,
'profile'
,
'email'
],
'scope'
:
[
'openid'
,
'profile'
,
'email'
,
'credentials'
],
'redirect_uri'
:
self
.
redirect_uri
,
'state'
:
state
,
}
...
...
@@ -79,33 +79,45 @@ def default_idp():
class
OIDCTokenAuthBackend
(
object
):
def
get_userinfo
(
self
,
request
,
access_token
,
token_type
=
'Bearer'
):
idp_id
=
utils
.
get_session
(
request
,
'idp_id'
,
None
)
idp
=
OIDCConfig
.
objects
.
get
(
id
=
idp_id
)
req
=
Request
(
idp
.
oidc_client
.
provider_info
[
'userinfo_endpoint'
]
)
auth
=
(
token_type
+
' '
+
access_token
)
req
.
add_header
(
'Authorization'
,
auth
)
def
get_userinfo
(
self
,
request
,
oidc_client
,
access_token
=
''
,
state
=
None
):
user_info
=
None
if
access_token
is
not
None
:
req
=
Request
(
oidc_client
.
provider_info
[
'userinfo_endpoint'
]
+
'?scope=openid&scope=profile'
,
)
auth
=
(
'Bearer '
+
access_token
)
req
.
add_header
(
'Authorization'
,
auth
)
userinfo_bytes
=
urlopen
(
req
).
read
()
user_info
=
json
.
loads
(
userinfo_bytes
.
decode
(
'UTF-8'
))
else
:
LOGGER
.
error
(
"Invalid parameters for get_userinfo"
)
userinfo_bytes
=
urlopen
(
req
).
read
()
return
json
.
loads
(
userinfo_bytes
.
decode
(
'UTF-8'
))
LOGGER
.
debug
(
"Got user info:
\n
%s
\n
"
,
user_info
)
return
user_info
def
authenticate
(
self
,
request
,
token
=
None
):
if
token
is
None
:
return
None
# get the user info from the idp
userinfo
=
self
.
get_userinfo
(
request
,
token
)
idp_id
=
utils
.
get_session
(
request
,
'idp_id'
,
None
)
idp
=
OIDCConfig
.
objects
.
get
(
id
=
idp_id
)
oidc_client
=
OIDCConfig
.
objects
.
get
(
id
=
idp_id
)
# get the user info from the idp
userinfo
=
self
.
get_userinfo
(
request
,
oidc_client
,
access_token
=
token
,
)
try
:
return
models
.
User
.
get_user
(
userinfo
,
idp
,
oidc_client
,
)
except
Exception
as
exception
:
LOGGER
.
error
(
'OIDCTokenAuthBackend: error constructing user: %s'
,
exception
)
return
None
...
...
django_backend/backend/auth/v1/views.py
View file @
d45e4e76
...
...
@@ -64,9 +64,10 @@ class AuthCallback(View):
idp_id
=
utils
.
get_session
(
request
,
'idp_id'
,
default_idp
().
id
)
oidc_config
=
OIDCConfig
.
objects
.
get
(
id
=
idp_id
)
oidc_client
=
oidc_config
.
oidc_client
LOGGER
.
debug
(
'AuthCallback: %s returned from IdP %s'
,
state
,
oidc_config
)
aresp
=
oidc_c
onfig
.
oidc_c
lient
.
parse_response
(
aresp
=
oidc_client
.
parse_response
(
AuthorizationResponse
,
info
=
json
.
dumps
(
request
.
GET
),
)
...
...
@@ -79,17 +80,22 @@ class AuthCallback(View):
raise
AuthException
(
'AuthCallbackStates do not match'
)
ac_token_response
=
(
oidc_c
onfig
.
oidc_c
lient
.
do_access_token_request
(
state
=
aresp
[
'state'
]
,
oidc_client
.
do_access_token_request
(
state
=
state
,
request_args
=
{
'code'
:
aresp
[
'code'
]
},
)
)
# does fail with 'invalid_token'
# user_info = OIDC_CLIENT.do_user_info_request(
# statearesp['state'])
# FIXME 'email_verified' in user info is no boolean
# but oic expects it to be
#user_info = oidc_client.do_user_info_request(
# method="GET",
# state=state,
#)
#LOGGER.debug("EXPERIMENT: %s", user_info)
# user_info = self.get_user_info(ac_token_response['access_token'])
# try:
# u = models.User.objects.get(sub=user_info['sub'])
...
...
django_backend/backend/clientapi/urls.py
View file @
d45e4e76
...
...
@@ -5,4 +5,5 @@ URLPATTERNS = [
url
(
r
'^deployments'
,
views
.
DeploymentsView
.
as_view
()),
url
(
r
'^config'
,
views
.
ConfigurationView
.
as_view
()),
url
(
r
'^ack/(?P<task_id>\d+)'
,
views
.
AckView
.
as_view
()),
url
(
r
'^response'
,
views
.
ResponseView
.
as_view
()),
]
django_backend/backend/clientapi/views.py
View file @
d45e4e76
...
...
@@ -26,7 +26,6 @@ class ConfigurationView(views.APIView):
authentication_classes
=
AUTHENTICATION_CLASSES
def
get
(
self
,
request
):
response
=
{
'services'
:
ServiceSerializer
(
request
.
user
.
site
.
services
.
all
(),
...
...
@@ -36,7 +35,6 @@ class ConfigurationView(views.APIView):
RabbitMQInstance
.
load
(),
).
data
,
}
LOGGER
.
debug
(
'Config: %s'
,
response
)
return
Response
(
response
)
...
...
@@ -48,8 +46,41 @@ class AckView(views.APIView):
for
item
in
request
.
user
.
site
.
task_items
.
all
():
if
item
.
task
.
id
==
int
(
task_id
):
item
.
task
.
item_finished
(
request
.
user
.
site
)
LOGGER
.
debug
(
'Got acknowledgement for task %s'
,
task_id
)
return
Response
({
'ok'
:
True
})
# this is no critical
LOGGER
.
info
(
'%s executed the obsolete task#%s'
,
request
.
user
,
task_id
)
return
Response
({
'ok'
:
True
})
class
ResponseView
(
views
.
APIView
):
authentication_classes
=
AUTHENTICATION_CLASSES
def
post
(
self
,
request
):
status
=
request
.
data
[
'output'
][
'status'
]
task_id
=
request
.
data
[
'id'
]
LOGGER
.
debug
(
'%s responded to task %s:
\n
%s'
,
request
.
user
,
task_id
,
request
.
data
)
# find the corresponding task for this item
task_item
=
None
for
item
in
request
.
user
.
site
.
task_items
.
all
():
if
item
.
task
.
id
==
int
(
task_id
):
task_item
=
item
if
task_item
is
not
None
:
if
status
==
'success'
:
task_item
.
task
.
item_finished
(
request
.
user
.
site
)
return
Response
({
'ok'
:
True
})
elif
status
==
'fail'
:
task_item
.
task
.
item_failed
(
request
.
user
.
site
)
return
Response
({
'ok'
:
True
})
elif
status
==
'reject'
:
task_item
.
task
.
item_rejected
(
request
.
user
.
site
)
return
Response
({
'ok'
:
True
})
LOGGER
.
info
(
'%s executed the obsolete task#%s'
,
request
.
user
,
task_id
)
return
Response
({
'ok'
:
False
})
django_backend/backend/frontend/serializers.py
View file @
d45e4e76
...
...
@@ -32,6 +32,7 @@ class DeploymentTaskItemSerializer(serializers.ModelSerializer):
'key'
,
'service'
,
'site'
,
'state'
,
]
...
...
django_backend/backend/models.py
View file @
d45e4e76
...
...
@@ -102,7 +102,7 @@ class RabbitMQInstance(SingletonModel):
def
_init_connection
(
self
):
global
RABBITMQ_CONNECTION
LOGGER
.
info
(
'Opening new BlockingConnection'
)
#LOGGER.debug
('Opening new BlockingConnection')
RABBITMQ_CONNECTION
=
pika
.
BlockingConnection
(
pika
.
ConnectionParameters
(
host
=
self
.
host
,
...
...
@@ -141,7 +141,7 @@ class RabbitMQInstance(SingletonModel):
def
_publish
(
self
,
exchange
,
routing_key
,
body
):
channel
=
self
.
_channel
self
.
_
channel
.
basic_publish
(
channel
.
basic_publish
(
exchange
=
exchange
,
routing_key
=
routing_key
,
body
=
body
,
...
...
@@ -246,17 +246,16 @@ class User(AbstractUser):
sub
=
userinfo
[
'sub'
]
if
'email'
not
in
userinfo
:
if
'name'
not
in
userinfo
:
raise
Exception
(
'Missing attributes in userinfo: email and name'
)
username
=
userinfo
[
'name'
]
username
=
sub
else
:
username
=
userinfo
[
'email'
]
email
=
userinfo
[
'email'
]
user
=
cls
(
user_type
=
'oidcuser'
,
username
=
username
,
sub
=
sub
,
email
=
email
,
idp
=
idp
,
userinfo
=
userinfo
,
)
...
...
@@ -715,6 +714,17 @@ class DeploymentTask(models.Model):
msg
,
)
# update the state of the remote webpage
def
send_state_update
(
self
):
from
.frontend.views
import
user_state_dict
content
=
{
'user_state'
:
user_state_dict
(
self
.
user
),
}
RabbitMQInstance
.
load
().
publish_to_webpage
(
self
.
user
,
content
,
)
# the client acked the receipt and execution of the task for his site
def
item_finished
(
self
,
site
):
...
...
@@ -726,14 +736,31 @@ class DeploymentTask(models.Model):
# finished sends its own message
self
.
_finished
()
else
:
from
.frontend.views
import
user_state_dict
content
=
{
'user_state'
:
user_state_dict
(
self
.
user
),
}
RabbitMQInstance
.
load
().
publish_to_webpage
(
self
.
user
,
content
,
)
self
.
send_state_update
()
# the client failed to execute the item
# the client can try again later
# we signal the user about the failure
def
item_failed
(
self
,
site
):
item
=
self
.
task_items
.
get
(
site
=
site
)
item
.
state
=
'failed'
item
.
save
()
self
.
send_state_update
()
# TODO implement
# the client failed to execute the item
# the client needs additional information from the user to try again
# we have to ask the user for data
def
item_rejected
(
self
,
site
):
item
=
self
.
task_items
.
get
(
site
=
site
)
item
.
state
=
'rejected'
item
.
save
()
self
.
send_state_update
()
# TODO implement
# maintenance after all task items are done
def
_finished
(
self
):
...
...
@@ -778,6 +805,18 @@ class DeploymentTaskItem(models.Model):
related_name
=
'deployment_task_items'
,
on_delete
=
models
.
CASCADE
,
)
STATE_CHOICES
=
(
(
'pending'
,
'Pending'
),
(
'done'
,
'Done'
),
(
'chanceled'
,
'Chanceled'
),
(
'failed'
,
'Failed'
),
(
'rejected'
,
'Rejected'
),
)
state
=
models
.
CharField
(
max_length
=
20
,
choices
=
STATE_CHOICES
,
default
=
'pending'
,
)
@
property
def
service
(
self
):
...
...
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