clients.py 7.58 KB
Newer Older
1

2
import logging
Lukas Burgey's avatar
Lukas Burgey committed
3

4
from django.conf import settings
Lukas Burgey's avatar
Lukas Burgey committed
5
from django.urls import path
Lukas Burgey's avatar
Lukas Burgey committed
6
from django.core.exceptions import ImproperlyConfigured
7
8
from django.shortcuts import get_object_or_404
from rest_framework import generics, views, exceptions
9
from rest_framework.authentication import BasicAuthentication
10
from rest_framework.response import Response
Lukas Burgey's avatar
Lukas Burgey committed
11

12
13
from feudal.backend.auth.v1.models.vo import VO, Group, Entitlement

Lukas Burgey's avatar
Lukas Burgey committed
14
15
16
from feudal.backend.models import Site, Service, deployments
from feudal.backend.models.brokers import RabbitMQInstance
from feudal.backend.models.serializers import clients
17

18
19
20
LOGGER = logging.getLogger(__name__)


21
# authentication class for the client api
22
AUTHENTICATION_CLASSES = (BasicAuthentication, )
23

24

25
class DeploymentStateView(generics.UpdateAPIView):
Lukas Burgey's avatar
Lukas Burgey committed
26
    authentication_classes = AUTHENTICATION_CLASSES
27
    serializer_class = clients.DeploymentStateSerializer
28

29
30
31
    def get_object(self):
        if 'id' not in self.request.data:
            raise exceptions.ValidationError('Need "id"')
32

33
34
35
36
        return get_object_or_404(
            self.request.user.site.states.all(),
            id=self.request.data.get('id'),
        )
37

38
    def perform_update(self, serializer):
39
40
        state = serializer.save()

Lukas Burgey's avatar
Lukas Burgey committed
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
        if state.user is not None:
            LOGGER.log(
                settings.AUDIT_LOG_LEVEL,
                '%s@%s - %s @ %s - response - %s - %s',
                state.user.sub,
                state.user.idp.issuer_uri,
                state.service.name,
                state.site.name,
                state.state,
                state.message,
            )
        else:
            LOGGER.log(
                settings.AUDIT_LOG_LEVEL,
                '%s @ %s - response - %s - %s',
                state.service.name,
                state.site.name,
                state.state,
                state.message,
            )
61
62


Lukas Burgey's avatar
Lukas Burgey committed
63
64
65
        # update the credential states of this deployment state
        state.client_credential_states(self.request.data.get('credential_states', {}))

66
67
68
69
70
        if str(state.state) == str(deployments.NOT_DEPLOYED) and state.is_orphaned:
            LOGGER.debug(state.msg('Deleted'))
            state.delete()
            return

71
        LOGGER.debug(state.msg('Patched by client: {} - {}'.format(state.state, state.message)))
Lukas Burgey's avatar
Lukas Burgey committed
72

73
        state.publish_to_user()
Lukas Burgey's avatar
Lukas Burgey committed
74

75
76
77
78
79
80
81
82
83
84
85

class DeploymentStateListView(generics.ListAPIView):
    authentication_classes = AUTHENTICATION_CLASSES
    serializer_class = clients.DeploymentStateSerializer

    def get_queryset(self):
        return [
            state
            for state in self.request.user.site.states.all()
            if state.is_pending
        ]
Lukas Burgey's avatar
Lukas Burgey committed
86
87


88
# the client has to fetch the configuration
89
class ConfigurationView(views.APIView):
Lukas Burgey's avatar
Lukas Burgey committed
90
    authentication_classes = AUTHENTICATION_CLASSES
91

92
93
94
95
96
97
98
99
100
    sid_to_service = {}

    @property
    def service_names(self):
        return [
            service.name
            for service in self.sid_to_service.values()
        ]

Lukas Burgey's avatar
Lukas Burgey committed
101
    @staticmethod
102
103
104
105
106
    def add_vo_to_service(service, vo):
        try:
            service.vos.get(name=vo.name)
        except VO.DoesNotExist:
            service.vos.add(vo)
107

108
109
110
111
        # we need to update the Deployments here
        for dep in vo.vo_deployments.all():
            dep.update()

Lukas Burgey's avatar
Lukas Burgey committed
112

113
114
115
    # returns the service ID to service mapping contained in the request
    def parse_sid_to_service(self, request):
        self.sid_to_service = {}
Lukas Burgey's avatar
Lukas Burgey committed
116
        services = request.data.get('services', None)
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
        if services is None:
            # maybe fail?
            return

        for service_id in services:
            service_descriptor = services.get(service_id, {})
            if 'name' in service_descriptor:
                # construct the service object
                self.sid_to_service[service_id] = Service.get_service(
                    service_descriptor.get('name', ''),
                    request.user.site,
                    description=service_descriptor.get('description', ''),
                    contact_email=service_descriptor.get('contact_email', ''),
                    contact_description=service_descriptor.get('contact_description', ''),
                )
132

133
134
    # adds / removes vos from services as needed to match the clients config
    def apply_vos_to_services(self, request):
135
        # apply groups / entitlements to the services
Lukas Burgey's avatar
Lukas Burgey committed
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
        group_to_service_ids = request.data.get('group_to_service_ids', None)
        if group_to_service_ids is not None:
            for group_name in group_to_service_ids:
                group = Group.get_group(name=group_name)

                service_ids = group_to_service_ids.get(group_name, None)
                if service_ids is not None:
                    for service_id in service_ids:
                        service = self.sid_to_service.get(service_id, None)
                        if service is not None:
                            self.add_vo_to_service(service, group)

        entitlement_to_service_ids = request.data.get('entitlement_to_service_ids', None)
        if entitlement_to_service_ids is not None:
            for entitlement_name in entitlement_to_service_ids:
                entitlement = Entitlement.get_entitlement(name=entitlement_name)

                service_ids = entitlement_to_service_ids.get(entitlement_name, None)
                if service_ids is not None:
                    for service_id in service_ids:
                        service = self.sid_to_service.get(service_id, None)
                        if service is not None:
                            self.add_vo_to_service(service, entitlement)
159
160
161
162
163
164
165
166
167
168

    # the client puts its (stripped) config
    def put(self, request):

        # the site where client is located
        try:
            request.user.site
        except Site.DoesNotExist:
            raise ImproperlyConfigured('client has no site')

Lukas Burgey's avatar
Lukas Burgey committed
169
170
        LOGGER.debug('Client PUT config: %s', request.data)

171
172
173
174
175
176
177
178
179
        # load the services
        self.parse_sid_to_service(request)

        # check if the client stopped providing services
        for old_service in request.user.site.services.all():
            if old_service.name not in self.service_names:
                old_service.remove_service()

        self.apply_vos_to_services(request)
180

Lukas Burgey's avatar
Lukas Burgey committed
181
        # initialize the broker, just in case
Lukas Burgey's avatar
Lukas Burgey committed
182
        broker = RabbitMQInstance.load()
183
184
        broker.initialize()

185
        return Response({
186
            'rabbitmq_config': clients.RabbitMQInstanceSerializer(
187
                broker,
188
            ).data,
Lukas Burgey's avatar
Lukas Burgey committed
189
            'site': request.user.site.name,
190
        })
191
192


Lukas Burgey's avatar
Lukas Burgey committed
193
194
195
196
197
198
199
200
201
class DeregisterView(views.APIView):
    authentication_classes = AUTHENTICATION_CLASSES

    def put(self, request):

        # the site where client is located
        client_site = None
        try:
            client_site = request.user.site
Lukas Burgey's avatar
Lukas Burgey committed
202
        except Site.DoesNotExist:
Lukas Burgey's avatar
Lukas Burgey committed
203
204
205
206
207
208
209
210
211
212
213
214
215
216
            raise ImproperlyConfigured('client has no site')

        # deregister the client / its services / its site
        # we expect all deployments at the clients to be removed (and therefore simply delete them all)
        LOGGER.info('[DEREG] Client %s from site %s is deregistering', request.user, client_site)

        for service in client_site.services.all():
            LOGGER.info('[DEREG] Triggering service removal %s', service)
            service.remove_service()

        response = 'Success deregistering'
        return Response(response)


217
218
219
220
221
def response_view_error(err):
    return Response(
        data={'error': err},
        status=500,
    )
Lukas Burgey's avatar
Lukas Burgey committed
222
223
224


URLPATTERNS = [
225
226
    path('dep-state/', DeploymentStateView.as_view()),
    path('dep-states/', DeploymentStateListView.as_view()),
Lukas Burgey's avatar
Lukas Burgey committed
227

228
229
    path('config/', ConfigurationView.as_view()),
    path('deregister/', DeregisterView.as_view()),
Lukas Burgey's avatar
Lukas Burgey committed
230
]