Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
7
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
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):
...
@@ -55,7 +55,7 @@ class OIDCConfig(db_models.Model):
args
=
{
args
=
{
'client_id'
:
self
.
client_id
,
'client_id'
:
self
.
client_id
,
'response_type'
:
'code'
,
'response_type'
:
'code'
,
'scope'
:
[
'openid'
,
'profile'
,
'email'
],
'scope'
:
[
'openid'
,
'profile'
,
'email'
,
'credentials'
],
'redirect_uri'
:
self
.
redirect_uri
,
'redirect_uri'
:
self
.
redirect_uri
,
'state'
:
state
,
'state'
:
state
,
}
}
...
@@ -79,33 +79,45 @@ def default_idp():
...
@@ -79,33 +79,45 @@ def default_idp():
class
OIDCTokenAuthBackend
(
object
):
class
OIDCTokenAuthBackend
(
object
):
def
get_userinfo
(
self
,
request
,
access_token
,
token_type
=
'Bearer'
):
def
get_userinfo
(
self
,
request
,
oidc_client
,
access_token
=
''
,
state
=
None
):
idp_id
=
utils
.
get_session
(
request
,
'idp_id'
,
None
)
user_info
=
None
idp
=
OIDCConfig
.
objects
.
get
(
id
=
idp_id
)
req
=
Request
(
if
access_token
is
not
None
:
idp
.
oidc_client
.
provider_info
[
'userinfo_endpoint'
]
req
=
Request
(
)
oidc_client
.
provider_info
[
'userinfo_endpoint'
]
+
'?scope=openid&scope=profile'
,
auth
=
(
token_type
+
' '
+
access_token
)
)
req
.
add_header
(
'Authorization'
,
auth
)
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
):
def
authenticate
(
self
,
request
,
token
=
None
):
if
token
is
None
:
if
token
is
None
:
return
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_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
:
try
:
return
models
.
User
.
get_user
(
return
models
.
User
.
get_user
(
userinfo
,
userinfo
,
id
p
,
o
id
c_client
,
)
)
except
Exception
as
exception
:
except
Exception
as
exception
:
LOGGER
.
error
(
'OIDCTokenAuthBackend: error constructing user: %s'
,
exception
)
LOGGER
.
error
(
'OIDCTokenAuthBackend: error constructing user: %s'
,
exception
)
return
None
return
None
...
...
django_backend/backend/auth/v1/views.py
View file @
d45e4e76
...
@@ -64,9 +64,10 @@ class AuthCallback(View):
...
@@ -64,9 +64,10 @@ class AuthCallback(View):
idp_id
=
utils
.
get_session
(
request
,
'idp_id'
,
default_idp
().
id
)
idp_id
=
utils
.
get_session
(
request
,
'idp_id'
,
default_idp
().
id
)
oidc_config
=
OIDCConfig
.
objects
.
get
(
id
=
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
)
LOGGER
.
debug
(
'AuthCallback: %s returned from IdP %s'
,
state
,
oidc_config
)
aresp
=
oidc_config
.
oidc_client
.
parse_response
(
aresp
=
oidc_client
.
parse_response
(
AuthorizationResponse
,
AuthorizationResponse
,
info
=
json
.
dumps
(
request
.
GET
),
info
=
json
.
dumps
(
request
.
GET
),
)
)
...
@@ -79,17 +80,22 @@ class AuthCallback(View):
...
@@ -79,17 +80,22 @@ class AuthCallback(View):
raise
AuthException
(
'AuthCallbackStates do not match'
)
raise
AuthException
(
'AuthCallbackStates do not match'
)
ac_token_response
=
(
ac_token_response
=
(
oidc_config
.
oidc_client
.
do_access_token_request
(
oidc_client
.
do_access_token_request
(
state
=
aresp
[
'
state
'
]
,
state
=
state
,
request_args
=
{
request_args
=
{
'code'
:
aresp
[
'code'
]
'code'
:
aresp
[
'code'
]
},
},
)
)
)
)
# does fail with 'invalid_token'
# FIXME 'email_verified' in user info is no boolean
# user_info = OIDC_CLIENT.do_user_info_request(
# but oic expects it to be
# statearesp['state'])
#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'])
# user_info = self.get_user_info(ac_token_response['access_token'])
# try:
# try:
# u = models.User.objects.get(sub=user_info['sub'])
# u = models.User.objects.get(sub=user_info['sub'])
...
...
django_backend/backend/clientapi/urls.py
View file @
d45e4e76
...
@@ -5,4 +5,5 @@ URLPATTERNS = [
...
@@ -5,4 +5,5 @@ URLPATTERNS = [
url
(
r
'^deployments'
,
views
.
DeploymentsView
.
as_view
()),
url
(
r
'^deployments'
,
views
.
DeploymentsView
.
as_view
()),
url
(
r
'^config'
,
views
.
ConfigurationView
.
as_view
()),
url
(
r
'^config'
,
views
.
ConfigurationView
.
as_view
()),
url
(
r
'^ack/(?P<task_id>\d+)'
,
views
.
AckView
.
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):
...
@@ -26,7 +26,6 @@ class ConfigurationView(views.APIView):
authentication_classes
=
AUTHENTICATION_CLASSES
authentication_classes
=
AUTHENTICATION_CLASSES
def
get
(
self
,
request
):
def
get
(
self
,
request
):
response
=
{
response
=
{
'services'
:
ServiceSerializer
(
'services'
:
ServiceSerializer
(
request
.
user
.
site
.
services
.
all
(),
request
.
user
.
site
.
services
.
all
(),
...
@@ -36,7 +35,6 @@ class ConfigurationView(views.APIView):
...
@@ -36,7 +35,6 @@ class ConfigurationView(views.APIView):
RabbitMQInstance
.
load
(),
RabbitMQInstance
.
load
(),
).
data
,
).
data
,
}
}
LOGGER
.
debug
(
'Config: %s'
,
response
)
return
Response
(
response
)
return
Response
(
response
)
...
@@ -48,8 +46,41 @@ class AckView(views.APIView):
...
@@ -48,8 +46,41 @@ class AckView(views.APIView):
for
item
in
request
.
user
.
site
.
task_items
.
all
():
for
item
in
request
.
user
.
site
.
task_items
.
all
():
if
item
.
task
.
id
==
int
(
task_id
):
if
item
.
task
.
id
==
int
(
task_id
):
item
.
task
.
item_finished
(
request
.
user
.
site
)
item
.
task
.
item_finished
(
request
.
user
.
site
)
LOGGER
.
debug
(
'Got acknowledgement for task %s'
,
task_id
)
return
Response
({
'ok'
:
True
})
return
Response
({
'ok'
:
True
})
# this is no critical
# this is no critical
LOGGER
.
info
(
'%s executed the obsolete task#%s'
,
request
.
user
,
task_id
)
LOGGER
.
info
(
'%s executed the obsolete task#%s'
,
request
.
user
,
task_id
)
return
Response
({
'ok'
:
True
})
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):
...
@@ -32,6 +32,7 @@ class DeploymentTaskItemSerializer(serializers.ModelSerializer):
'key'
,
'key'
,
'service'
,
'service'
,
'site'
,
'site'
,
'state'
,
]
]
...
...
django_backend/backend/models.py
View file @
d45e4e76
...
@@ -102,7 +102,7 @@ class RabbitMQInstance(SingletonModel):
...
@@ -102,7 +102,7 @@ class RabbitMQInstance(SingletonModel):
def
_init_connection
(
self
):
def
_init_connection
(
self
):
global
RABBITMQ_CONNECTION
global
RABBITMQ_CONNECTION
LOGGER
.
info
(
'Opening new BlockingConnection'
)
#
LOGGER.
debug
('Opening new BlockingConnection')
RABBITMQ_CONNECTION
=
pika
.
BlockingConnection
(
RABBITMQ_CONNECTION
=
pika
.
BlockingConnection
(
pika
.
ConnectionParameters
(
pika
.
ConnectionParameters
(
host
=
self
.
host
,
host
=
self
.
host
,
...
@@ -141,7 +141,7 @@ class RabbitMQInstance(SingletonModel):
...
@@ -141,7 +141,7 @@ class RabbitMQInstance(SingletonModel):
def
_publish
(
self
,
exchange
,
routing_key
,
body
):
def
_publish
(
self
,
exchange
,
routing_key
,
body
):
channel
=
self
.
_channel
channel
=
self
.
_channel
self
.
_
channel
.
basic_publish
(
channel
.
basic_publish
(
exchange
=
exchange
,
exchange
=
exchange
,
routing_key
=
routing_key
,
routing_key
=
routing_key
,
body
=
body
,
body
=
body
,
...
@@ -246,17 +246,16 @@ class User(AbstractUser):
...
@@ -246,17 +246,16 @@ class User(AbstractUser):
sub
=
userinfo
[
'sub'
]
sub
=
userinfo
[
'sub'
]
if
'email'
not
in
userinfo
:
if
'email'
not
in
userinfo
:
if
'name'
not
in
userinfo
:
username
=
sub
raise
Exception
(
'Missing attributes in userinfo: email and name'
)
username
=
userinfo
[
'name'
]
else
:
else
:
username
=
userinfo
[
'email'
]
username
=
userinfo
[
'email'
]
email
=
userinfo
[
'email'
]
user
=
cls
(
user
=
cls
(
user_type
=
'oidcuser'
,
user_type
=
'oidcuser'
,
username
=
username
,
username
=
username
,
sub
=
sub
,
sub
=
sub
,
email
=
email
,
idp
=
idp
,
idp
=
idp
,
userinfo
=
userinfo
,
userinfo
=
userinfo
,
)
)
...
@@ -715,6 +714,17 @@ class DeploymentTask(models.Model):
...
@@ -715,6 +714,17 @@ class DeploymentTask(models.Model):
msg
,
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
# the client acked the receipt and execution of the task for his site
def
item_finished
(
self
,
site
):
def
item_finished
(
self
,
site
):
...
@@ -726,14 +736,31 @@ class DeploymentTask(models.Model):
...
@@ -726,14 +736,31 @@ class DeploymentTask(models.Model):
# finished sends its own message
# finished sends its own message
self
.
_finished
()
self
.
_finished
()
else
:
else
:
from
.frontend.views
import
user_state_dict
self
.
send_state_update
()
content
=
{
'user_state'
:
user_state_dict
(
self
.
user
),
# the client failed to execute the item
}
# the client can try again later
RabbitMQInstance
.
load
().
publish_to_webpage
(
# we signal the user about the failure
self
.
user
,
def
item_failed
(
self
,
site
):
content
,
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
# maintenance after all task items are done
def
_finished
(
self
):
def
_finished
(
self
):
...
@@ -778,6 +805,18 @@ class DeploymentTaskItem(models.Model):
...
@@ -778,6 +805,18 @@ class DeploymentTaskItem(models.Model):
related_name
=
'deployment_task_items'
,
related_name
=
'deployment_task_items'
,
on_delete
=
models
.
CASCADE
,
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
@
property
def
service
(
self
):
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