Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
feudal
feudalBackend
Commits
9ffd9126
Commit
9ffd9126
authored
Apr 26, 2018
by
Lukas Burgey
Browse files
Implement message transport to the webpages
parent
caacb301
Changes
6
Hide whitespace changes
Inline
Side-by-side
django_backend/backend/auth/v1/client_views.py
View file @
9ffd9126
...
...
@@ -3,102 +3,215 @@ import logging
import
re
from
django.http
import
HttpResponse
from
django.contrib.auth
import
authenticate
from
django.contrib.sessions.models
import
Session
from
...models
import
User
,
RabbitMQInstance
LOGGER
=
logging
.
getLogger
(
__name__
)
ALLOW
=
HttpResponse
(
'allow'
)
DENY
=
HttpResponse
(
'deny'
)
def
_valid_vhost
(
request
):
if
'vhost'
in
request
.
POST
and
request
.
POST
[
'vhost'
]
==
RabbitMQInstance
.
load
().
vhost
:
if
request
.
POST
.
get
(
'vhost'
)
==
RabbitMQInstance
.
load
().
vhost
:
return
True
LOGGER
.
error
(
'illegal vhost requested'
)
return
False
def
_valid_permission
(
request
):
if
'permission'
in
request
.
POST
and
request
.
POST
[
'permission'
]
!=
'write'
:
perm
=
request
.
POST
.
get
(
'permission'
)
if
perm
!=
'write'
:
return
True
LOGGER
.
error
(
'illegal permission requested
'
)
LOGGER
.
error
(
'illegal permission requested
%s'
,
perm
)
return
False
def
_valid_user
(
request
):
if
'username'
in
request
.
POST
:
valid
=
User
.
objects
.
filter
(
user_type
=
'apiclient'
,
username
=
request
.
POST
[
'username'
],
).
exists
()
if
valid
:
return
True
LOGGER
.
error
(
'illegal user requested'
)
return
_apiclient_valid
(
request
)
or
_webpage_client_userid
(
request
)
def
_apiclient_valid
(
request
):
valid
=
User
.
objects
.
filter
(
user_type
=
'apiclient'
,
username
=
request
.
POST
.
get
(
'username'
),
).
exists
()
if
valid
:
return
True
return
False
def
_get_user
(
request
):
if
'username'
in
request
.
POST
:
user
=
User
.
objects
.
filter
(
user_type
=
'apiclient'
).
get
(
username
=
request
.
POST
[
'username'
])
if
user
:
return
user
def
_apiclient_get
(
request
):
user
=
User
.
objects
.
filter
(
user_type
=
'apiclient'
,
).
get
(
username
=
request
.
POST
.
get
(
'username'
),
)
if
user
:
return
user
LOGGER
.
error
(
'unable to get user for request'
)
return
None
# VIEWS
# client authentication for RabbitMQ
def
user_endpoint
(
request
):
if
'username'
in
request
.
POST
and
'password'
in
request
.
POST
:
username
=
request
.
POST
[
'username'
]
password
=
request
.
POST
[
'password'
]
user
=
authenticate
(
username
=
username
,
password
=
password
)
if
user
:
LOGGER
.
info
(
'Authenticated client as %s'
,
user
)
return
HttpResponse
(
"allow"
)
def
_webpage_client_userid
(
request
):
username
=
request
.
POST
.
get
(
'username'
)
if
username
.
startswith
(
'webpage-client:'
):
components
=
username
.
split
(
':'
,
maxsplit
=
1
)
if
len
(
components
)
==
2
:
return
components
[
1
]
return
''
def
_webpage_client_session
(
request
):
query
=
Session
.
objects
.
filter
(
session_key
=
request
.
POST
.
get
(
'password'
),
)
if
query
.
exists
()
and
len
(
query
)
==
1
:
return
query
.
first
()
return
None
LOGGER
.
error
(
'Failed to authenticate user for RabbitMQ'
)
return
HttpResponse
(
"deny"
)
def
_webpage_client_valid
(
request
):
userid
=
_webpage_client_userid
(
request
)
session
=
_webpage_client_session
(
request
)
if
(
_webpage_client_userid
(
request
)
!=
''
and
session
.
get_decoded
().
get
(
'_auth_user_id'
)
==
userid
):
return
True
# client authorization checks for RabbitMQ
def
vhost
(
request
):
# check if on the correct virtual host
if
_valid_vhost
(
request
)
and
_valid_user
(
request
):
return
HttpResponse
(
"allow"
)
LOGGER
.
error
(
'Failed to authenticate webpage client for RabbitMQ'
)
return
False
LOGGER
.
error
(
'Authorization check for vhost failed for %s'
,
request
.
POST
)
return
HttpResponse
(
"deny"
)
# VIEWS: authentication and authorization for
# apiclients and webpage-clients
def
resource
(
request
):
if
not
_valid_vhost
(
request
)
or
not
_valid_user
(
request
):
return
HttpResponse
(
'deny'
)
def
user_endpoint
(
request
):
if
_webpage_client_valid
(
request
):
LOGGER
.
info
(
'Authenticated webpage client'
)
return
ALLOW
if
'resource'
in
request
.
POST
and
'name'
in
request
.
POST
:
if
request
.
POST
[
'resource'
]
==
'queue'
:
# the temporary queue a client binds to our exchange
if
request
.
POST
[
'name'
].
startswith
(
'amq.gen-'
):
return
HttpResponse
(
'allow'
)
elif
request
.
POST
[
'resource'
]
==
'exchange'
and
_valid_permission
(
request
):
# our exchange
if
request
.
POST
[
'name'
]
==
RabbitMQInstance
.
load
().
exchange
:
return
HttpResponse
(
'allow'
)
elif
request
.
POST
[
'resource'
]
==
'topic'
and
_valid_permission
(
request
):
pass
user
=
authenticate
(
username
=
request
.
POST
.
get
(
'username'
),
password
=
request
.
POST
.
get
(
'password'
),
)
if
user
:
LOGGER
.
info
(
'Authenticated client as %s'
,
user
)
return
ALLOW
LOGGER
.
error
(
'Authorization check for resource failed for %s'
,
request
.
POST
)
return
HttpResponse
(
'deny'
)
LOGGER
.
error
(
'Failed to authenticate user for RabbitMQ'
)
return
DENY
def
topic
(
request
):
def
vhost_endpoint
(
request
):
# check if on the correct virtual host
if
not
_valid_vhost
(
request
)
or
not
_valid_permission
(
request
)
or
not
_valid_user
(
request
):
return
HttpResponse
(
'deny'
)
if
_valid_vhost
(
request
)
and
_valid_user
(
request
):
return
ALLOW
user
=
_get_user
(
request
)
if
user
and
'routing_key'
in
request
.
POST
:
routing_key
=
request
.
POST
[
'routing_key'
]
LOGGER
.
error
(
'Authorization check for vhost failed for %s'
,
request
.
POST
)
return
DENY
def
_resource_authorized_webpage_client
(
request
):
resource
=
request
.
POST
.
get
(
'resource'
)
name
=
request
.
POST
.
get
(
'name'
,
''
)
permission
=
request
.
POST
.
get
(
'permission'
,
[])
return
(
resource
==
'exchange'
and
name
==
'update'
and
not
'write'
in
permission
)
or
(
resource
==
'queue'
and
name
.
startswith
(
'stomp-subscription-'
)
)
or
(
resource
==
'topic'
and
name
==
_webpage_client_userid
(
request
)
)
def
_resource_authorized_apiclient
(
request
):
resource
=
request
.
POST
.
get
(
'resource'
)
name
=
request
.
POST
.
get
(
'name'
,
''
)
permission
=
request
.
POST
.
get
(
'permission'
,
[])
return
(
resource
==
'queue'
and
name
.
startswith
(
'amq.gen-'
)
)
or
(
resource
==
'exchange'
and
name
==
RabbitMQInstance
.
load
().
exchange
and
not
'write'
in
permission
)
def
resource_endpoint
(
request
):
resource
=
request
.
POST
.
get
(
'resource'
)
name
=
request
.
POST
.
get
(
'name'
,
''
)
permission
=
request
.
POST
.
get
(
'permission'
,
[])
if
_valid_vhost
(
request
):
if
(
_webpage_client_userid
(
request
)
and
_resource_authorized_webpage_client
(
request
)
):
LOGGER
.
debug
(
'Granted %s access to resource %s %s to client'
,
permission
,
resource
,
name
,
)
return
ALLOW
if
(
_apiclient_valid
(
request
)
and
_resource_authorized_apiclient
(
request
)
):
LOGGER
.
debug
(
'Granted %s access to resource %s %s to client'
,
permission
,
resource
,
name
,
)
return
ALLOW
LOGGER
.
error
(
'Authorization check of resource %s %s for client failed'
,
resource
,
name
,
)
return
DENY
def
topic_endpoint
(
request
):
permission
=
request
.
POST
.
get
(
'permission'
,
[])
resource
=
request
.
POST
.
get
(
'resource'
,
''
)
name
=
request
.
POST
.
get
(
'name'
,
''
)
routing_key
=
request
.
POST
.
get
(
'routing_key'
,
''
)
if
not
_valid_vhost
(
request
)
or
not
_valid_permission
(
request
):
return
DENY
webpage_client_userid
=
_webpage_client_userid
(
request
)
if
webpage_client_userid
:
if
(
routing_key
==
webpage_client_userid
and
not
'write'
in
permission
):
LOGGER
.
debug
(
'Granted %s access to %s %s to client'
,
permission
,
resource
,
routing_key
,
)
return
ALLOW
LOGGER
.
error
(
'Authorization check of resource %s %s for client failed'
,
resource
,
name
,
)
return
DENY
user
=
_apiclient_get
(
request
)
if
user
:
routing_key
=
request
.
POST
.
get
(
'routing_key'
,
''
)
if
routing_key
.
startswith
(
'service.'
):
match
=
re
.
search
(
'service.(.+)'
,
routing_key
)
if
match
:
service_name
=
match
.
group
(
1
)
for
service
in
user
.
site
.
services
.
all
():
if
service_name
==
service
.
name
:
return
HttpResponse
(
'allow'
)
return
ALLOW
LOGGER
.
error
(
'Authorization check for topic failed for %s'
,
request
.
POST
)
return
HttpResponse
(
'deny'
)
return
DENY
django_backend/backend/auth/v1/urls.py
View file @
9ffd9126
...
...
@@ -8,7 +8,7 @@ URLPATTERNS = [
url
(
r
'^callback'
,
views
.
AuthCallback
.
as_view
()),
url
(
r
'^logout'
,
views
.
LogoutView
.
as_view
()),
url
(
r
'^client/user'
,
csrf_exempt
(
client_views
.
user_endpoint
)),
url
(
r
'^client/vhost'
,
csrf_exempt
(
client_views
.
vhost
)),
url
(
r
'^client/resource'
,
csrf_exempt
(
client_views
.
resource
)),
url
(
r
'^client/topic'
,
csrf_exempt
(
client_views
.
topic
)),
url
(
r
'^client/vhost'
,
csrf_exempt
(
client_views
.
vhost
_endpoint
)),
url
(
r
'^client/resource'
,
csrf_exempt
(
client_views
.
resource
_endpoint
)),
url
(
r
'^client/topic'
,
csrf_exempt
(
client_views
.
topic
_endpoint
)),
]
django_backend/backend/frontend/serializers.py
View file @
9ffd9126
...
...
@@ -44,7 +44,7 @@ class UserSerializer(serializers.ModelSerializer):
class
Meta
:
model
=
models
.
User
fields
=
[
'email'
,
'userinfo'
,
'ssh_keys'
,
'groups'
,
'deployments'
,
'auth_groups'
]
fields
=
[
'id'
,
'email'
,
'userinfo'
,
'ssh_keys'
,
'groups'
,
'deployments'
,
'auth_groups'
]
class
ClientSerializer
(
serializers
.
HyperlinkedModelSerializer
):
...
...
django_backend/backend/frontend/views.py
View file @
9ffd9126
...
...
@@ -28,18 +28,22 @@ def _api_error_response():
# Response for StateView, LogoutView, and all post requests
def
_api_state_response
(
request
):
if
not
request
.
user
.
is_authenticated
:
return
Response
(
{
'logged_in'
:
False
,
}
)
response
=
{
'logged_in'
:
request
.
user
.
is_authenticated
'logged_in'
:
True
,
'user'
:
serializers
.
UserSerializer
(
request
.
user
).
data
,
'services'
:
serializers
.
ServiceSerializer
(
user_services
(
request
.
user
),
many
=
True
,
).
data
,
}
if
request
.
user
.
is_authenticated
:
response
[
'user'
]
=
serializers
.
UserSerializer
(
request
.
user
).
data
response
[
'services'
]
=
serializers
.
ServiceSerializer
(
user_services
(
request
.
user
),
many
=
True
,
).
data
if
'error'
in
request
.
session
:
response
[
'error'
]
=
request
.
session
[
'error'
]
...
...
@@ -122,6 +126,7 @@ class DeploymentView(views.APIView):
deployment
.
save
()
return
_api_state_response
(
request
)
class
UserDeletionView
(
views
.
APIView
):
def
delete
(
self
,
request
):
# this also logs out the user
...
...
django_backend/backend/models.py
View file @
9ffd9126
...
...
@@ -15,8 +15,11 @@ from django_mysql.models import JSONField
from
.auth.v1.models
import
OIDCConfig
LOGGER
=
logging
.
getLogger
(
__name__
)
# TODO dirty
RECONNECT_TIMEOUT
=
5
RECONNECT_RETRIES
=
3
RABBITMQ_CONNECTION
=
None
# singleton for simple configs
...
...
@@ -83,50 +86,83 @@ class RabbitMQInstance(SingletonModel):
self
.
password
,
)
def
_init_exchanges
(
self
,
channel
):
channel
.
exchange_declare
(
exchange
=
self
.
exchange
,
durable
=
True
,
auto_delete
=
False
,
exchange_type
=
'topic'
,
)
# TODO put in config
channel
.
exchange_declare
(
exchange
=
'update'
,
durable
=
True
,
auto_delete
=
False
,
exchange_type
=
'topic'
,
)
def
_init_connection
(
self
):
global
RABBITMQ_CONNECTION
LOGGER
.
info
(
'Opening new BlockingConnection'
)
RABBITMQ_CONNECTION
=
pika
.
BlockingConnection
(
pika
.
ConnectionParameters
(
host
=
self
.
host
,
ssl
=
True
,
heartbeat_interval
=
60
,
)
)
return
RABBITMQ_CONNECTION
@
property
def
_connection
(
self
):
global
RABBITMQ_CONNECTION
if
RABBITMQ_CONNECTION
is
not
None
:
if
RABBITMQ_CONNECTION
.
is_open
:
return
RABBITMQ_CONNECTION
elif
RABBITMQ_CONNECTION
.
is_closing
:
RABBITMQ_CONNECTION
.
close
()
connection
=
self
.
_init_connection
()
channel
=
connection
.
channel
()
self
.
_init_exchanges
(
connection
.
channel
())
channel
.
close
()
RABBITMQ_CONNECTION
=
connection
return
connection
@
property
def
_connection_parameters
(
self
):
return
pika
.
ConnectionParameters
(
host
=
self
.
host
,
ssl
=
True
,
def
_channel
(
self
):
channel
=
self
.
_connection
.
channel
()
channel
.
confirm_delivery
()
return
channel
def
_publish
(
self
,
exchange
,
routing_key
,
body
):
channel
=
self
.
_channel
self
.
_channel
.
basic_publish
(
exchange
=
exchange
,
routing_key
=
routing_key
,
body
=
body
,
properties
=
pika
.
BasicProperties
(
delivery_mode
=
1
,
),
)
channel
.
close
()
# PUBLIC API
# PUBLIC API
def
publish_by_service
(
self
,
service
,
msg
):
# FIXME dirty
tries
=
0
while
tries
<
RECONNECT_RETRIES
:
try
:
# open connection
connection
=
pika
.
BlockingConnection
(
self
.
_connection_parameters
,
)
# open channel
channel
=
connection
.
channel
()
channel
.
exchange_declare
(
exchange
=
self
.
exchange
,
durable
=
True
,
auto_delete
=
False
,
exchange_type
=
'topic'
,
)
channel
.
confirm_delivery
()
channel
.
basic_publish
(
exchange
=
self
.
exchange
,
routing_key
=
service
.
routing_key
,
body
=
msg
,
properties
=
pika
.
BasicProperties
(
delivery_mode
=
1
,
),
)
channel
.
close
()
connection
.
close
()
return
except
:
time
.
sleep
(
RECONNECT_TIMEOUT
)
self
.
_publish
(
self
.
exchange
,
service
.
routing_key
,
msg
,
)
tries
+=
1
def
publish_to_webpage
(
self
,
user
):
LOGGER
.
debug
(
'Signalling webpage of user %s'
,
user
)
self
.
_publish
(
'update'
,
str
(
user
.
id
),
'<got update>'
,
)
def
user_info_default
():
...
...
@@ -295,7 +331,6 @@ class User(AbstractUser):
for
dep
in
self
.
deployments
.
all
():
dep
.
activate
()
def
deactivate
(
self
):
if
not
self
.
_is_active
:
LOGGER
.
error
(
self
.
msg
(
'already deactivated'
))
...
...
@@ -674,6 +709,9 @@ class DeploymentTask(models.Model):
# the client acked the receipt and execution of the task for his site
def
item_finished
(
self
,
site
):
RabbitMQInstance
.
load
().
publish_to_webpage
(
self
.
user
,
)
item
=
self
.
task_items
.
get
(
site
=
site
)
LOGGER
.
debug
(
item
.
msg
(
'done'
))
item
.
delete
()
...
...
django_backend/settings.py
View file @
9ffd9126
...
...
@@ -17,7 +17,10 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
AUTH_USER_MODEL
=
'backend.User'
# cookie settings
SESSION_COOKIE_AGE
=
3600
SESSION_COOKIE_SECURE
=
True
SESSION_COOKIE_HTTPONLY
=
False
CSRF_HEADER_NAME
=
'HTTP_X_CSRFTOKEN'
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new 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