Projet

Général

Profil

0001-ozwillo-create-ozwillo-app-in-contrib-14935.patch

Jean-Baptiste Jaillet, 24 février 2017 17:37

Télécharger (23,6 ko)

Voir les différences:

Subject: [PATCH] ozwillo: create ozwillo app in contrib (#14935)

 hobo/contrib/__init__.py                           |   0
 hobo/contrib/ozwillo/README.txt                    |  22 +++
 hobo/contrib/ozwillo/__init__.py                   |   0
 .../ozwillo/examples/import-site-agents.json       |  30 ++++
 .../ozwillo/examples/import-site-template.json     | 169 +++++++++++++++++++++
 hobo/contrib/ozwillo/examples/template_recipe.json |  72 +++++++++
 .../contrib/ozwillo/scripts/create_user_ozwillo.py |  47 ++++++
 hobo/contrib/ozwillo/urls.py                       |  26 ++++
 hobo/contrib/ozwillo/views.py                      | 162 ++++++++++++++++++++
 hobo/urls.py                                       |   4 +
 10 files changed, 532 insertions(+)
 create mode 100644 hobo/contrib/__init__.py
 create mode 100644 hobo/contrib/ozwillo/README.txt
 create mode 100644 hobo/contrib/ozwillo/__init__.py
 create mode 100644 hobo/contrib/ozwillo/examples/import-site-agents.json
 create mode 100644 hobo/contrib/ozwillo/examples/import-site-template.json
 create mode 100644 hobo/contrib/ozwillo/examples/template_recipe.json
 create mode 100644 hobo/contrib/ozwillo/scripts/create_user_ozwillo.py
 create mode 100644 hobo/contrib/ozwillo/urls.py
 create mode 100644 hobo/contrib/ozwillo/views.py
hobo/contrib/ozwillo/README.txt
1
To run this plugin well, you have to set some files in /etc/hobo/ozwillo :
2
- the files are in the folder examples.
3
- it's a common recipe for your publik, and two extracts of user and agent combo.
4
- hobo has to be in the sudoers
5

  
6
You have to set several var in a settings.json too :
7
-OZWILLO_SECRET
8
-OZWILLO_ENV_DOMAIN (e.g: sictiam.dev.entrouvert.org)
9
-OZWILLO_DESTRUCTION_URI 
10
-OZWILLO_DESTRUCTION_SECRET
11
-OZWILLO_PLATEFORM (https://dev.entrouvert.org/projects/sictiam/wiki/Raccordement_OpenID_Connect_%C3%A0_Ozwillo for the values)
12

  
13
And finally you have to enable in INSTALLED_APPS hobo.contrib.ozwillo.
14

  
15
The views create-publik-instance receive an ozwillo request with some clients informations (secret and id), the ozwillo user sending the request, the organization name (which is the collectivity's name to deploy) and the registration uri (where you're supposed to POST when the job's done).
16
The script modify a template_recipe by replacing every 'instance_name' by the actual organization name, and same for the combo user extract (rewritting all the url_redirect fields).
17
The script then launch a cook and three commands :
18
- the import of the combo user with the modified extract
19
- the import of the combo agent
20
- a runscript creating a role (same as the one in wcs linked to the form sve 'agents sve'), a provider (the details are in the page linked for the parameter OZWILLO_PLATEFORM) and an admin User in Authentic who is the ozwillo user sending the request.
21
In the final acknolegment response, the script sends a 'services' dictionnary for ozillo to set some links and parameters in its backoffice about the app deployed).
22

  
hobo/contrib/ozwillo/examples/import-site-agents.json
1
[
2
    {
3
        "cells": [
4
            {
5
                "fields": {
6
                    "extra_css_class": "",
7
                    "groups": [],
8
                    "order": 1,
9
                    "placeholder": "content",
10
                    "public": true,
11
                    "restricted_to_unlogged": false,
12
                    "slug": "services",
13
                    "text": "<h2>Bienvenue</h2>\r\n"
14
                },
15
                "model": "data.textcell"
16
            }
17
        ],
18
        "fields": {
19
            "exclude_from_navigation": false,
20
            "groups": [],
21
            "order": 1,
22
            "parent": null,
23
            "public": false,
24
            "redirect_url": "",
25
            "slug": "index",
26
            "template_name": "standard",
27
            "title": "Accueil"
28
        }
29
    }
30
]
hobo/contrib/ozwillo/examples/import-site-template.json
1
[
2
    {
3
        "cells": [
4
            {
5
                "fields": {
6
                    "extra_css_class": "",
7
                    "groups": [],
8
                    "order": 0,
9
                    "placeholder": "content",
10
                    "public": true,
11
                    "restricted_to_unlogged": true,
12
                    "slug": "",
13
                    "text": "<h2>Bienvenue</h2>\r\n\r\n<p>Bienvenue sur votre compte usager.</p>\r\n\r\n<p>Pour suivre vos d&eacute;marches en cours, cr&eacute;ez votre compte personnel ou identifiez-vous depuis <a href=\"/login/\">la page de connexion</a>.</p>\r\n"
14
                },
15
                "model": "data.textcell"
16
            },
17
            {
18
                "fields": {
19
                    "extra_css_class": "",
20
                    "groups": [],
21
                    "order": 1,
22
                    "placeholder": "content",
23
                    "public": false,
24
                    "restricted_to_unlogged": false,
25
                    "slug": "",
26
                    "text": "<h2>Bienvenue</h2>\r\n\r\n<p>Bienvenue sur votre compte usager.</p>\r\n"
27
                },
28
                "model": "data.textcell"
29
            },
30
            {
31
                "fields": {
32
                    "extra_css_class": "",
33
                    "groups": [],
34
                    "order": 2,
35
                    "placeholder": "right",
36
                    "public": true,
37
                    "restricted_to_unlogged": false,
38
                    "slug": "",
39
                    "wcs_site": ""
40
                },
41
                "model": "wcs.trackingcodeinputcell"
42
            },
43
            {
44
                "fields": {
45
                    "extra_css_class": "",
46
                    "groups": [],
47
                    "order": 3,
48
                    "placeholder": "right",
49
                    "public": true,
50
                    "restricted_to_unlogged": true,
51
                    "slug": "",
52
                    "text": "<h2>Demandes en cours</h2>\r\n\r\n<p>Connectez-vous pour voir vos demandes en cours.</p>\r\n"
53
                },
54
                "model": "data.textcell"
55
            },
56
            {
57
                "fields": {
58
                    "current_forms": true,
59
                    "done_forms": false,
60
                    "extra_css_class": "",
61
                    "groups": [],
62
                    "order": 4,
63
                    "placeholder": "right",
64
                    "public": false,
65
                    "restricted_to_unlogged": false,
66
                    "slug": "",
67
                    "wcs_site": ""
68
                },
69
                "model": "wcs.wcscurrentformscell"
70
            },
71
            {
72
                "fields": {
73
                    "extra_css_class": "",
74
                    "groups": [],
75
                    "order": 5,
76
                    "placeholder": "footer",
77
                    "public": true,
78
                    "restricted_to_unlogged": false,
79
                    "slug": "",
80
                    "text": "<p>Ce service est propos&eacute; par le <a href=\"http://www.sictiam.fr/\">SICTIAM</a>.</p>\r\n"
81
                },
82
                "model": "data.textcell"
83
            },
84
            {
85
                "fields": {
86
                    "category_reference": "eservices:nous-contacter",
87
                    "extra_css_class": "",
88
                    "groups": [],
89
                    "limit": null,
90
                    "manual_order": {
91
                        "data": []
92
                    },
93
                    "order": 6,
94
                    "ordering": "popularity",
95
                    "placeholder": "content",
96
                    "public": true,
97
                    "restricted_to_unlogged": false,
98
                    "slug": ""
99
                },
100
                "model": "wcs.wcsformsofcategorycell"
101
            }
102
        ],
103
        "fields": {
104
            "exclude_from_navigation": false,
105
            "groups": [],
106
            "order": 1,
107
            "parent": null,
108
            "public": true,
109
            "redirect_url": "",
110
            "slug": "index",
111
            "template_name": "two-columns",
112
            "title": "Accueil"
113
        }
114
    },
115
    {
116
        "cells": [
117
            {
118
                "fields": {
119
                    "extra_css_class": "",
120
                    "groups": [],
121
                    "order": 0,
122
                    "placeholder": "footer",
123
                    "public": true,
124
                    "restricted_to_unlogged": false,
125
                    "slug": ""
126
                },
127
                "model": "data.parentcontentcell"
128
            }
129
        ],
130
        "fields": {
131
            "exclude_from_navigation": false,
132
            "groups": [],
133
            "order": 2,
134
            "parent": null,
135
            "public": true,
136
            "redirect_url": "https://connexion-instance_name.sictiam.dev.entrouvert.org/accounts/",
137
            "slug": "mon-compte",
138
            "template_name": "standard",
139
            "title": "Mon compte"
140
        }
141
    },
142
    {
143
        "cells": [
144
            {
145
                "fields": {
146
                    "extra_css_class": "",
147
                    "groups": [],
148
                    "order": 0,
149
                    "placeholder": "footer",
150
                    "public": true,
151
                    "restricted_to_unlogged": false,
152
                    "slug": ""
153
                },
154
                "model": "data.parentcontentcell"
155
            }
156
        ],
157
        "fields": {
158
            "exclude_from_navigation": false,
159
            "groups": [],
160
            "order": 3,
161
            "parent": null,
162
            "public": true,
163
            "redirect_url": "https://demarches-instance_name.sictiam.dev.entrouvert.org/saisine-par-voie-electronique/tryauth",
164
            "slug": "nous-contacter",
165
            "template_name": "standard",
166
            "title": "Nous contacter"
167
        }
168
    }
169
]
hobo/contrib/ozwillo/examples/template_recipe.json
1
{
2
   "steps" : [
3
      {
4
         "create-hobo" : {
5
            "url" : "https://${hobo}/"
6
         }
7
      },
8
      {
9
         "create-authentic" : {
10
            "title" : "Connexion",
11
            "url" : "https://${authentic}/"
12
         }
13
      },
14
      {
15
         "set-idp" : {}
16
      },
17
      {
18
         "create-superuser" : {
19
           "username" : "admin",
20
           "email" : "admin@example.net"
21
         }
22
      },
23
      {
24
         "create-combo" : {
25
            "template_name" : "portal-user",
26
            "title" : "Compte citoyen",
27
            "url" : "https://${combo}/"
28
         }
29
      },
30
      {
31
         "create-combo" : {
32
            "template_name" : "portal-agent",
33
            "title" : "Portail agent",
34
            "url" : "https://${combo_agent}/",
35
            "slug" : "portal-agent"
36
         }
37
      },
38
      {
39
         "create-wcs" : {
40
            "url" : "https://${wcs}/",
41
            "title" : "Démarches",
42
            "template_name": "export.wcs"
43
         }
44
      },
45
      {
46
	 "create-fargo" : {
47
	    "url" : "https://${fargo}/",
48
            "title" : "Porte documents"
49
         }
50
      },	 
51
      {
52
         "create-passerelle" : {
53
            "url" : "https://${passerelle}/",
54
            "title" : "Passerelle"
55
         }
56
      },
57
      {
58
       	 "set-theme" : {
59
                          "theme" : "publik"
60
                       }
61
      }
62
   ],
63
   "variables" : {
64
      "authentic" : "connexion-instance_name.sictiam.dev.entrouvert.org",
65
      "combo" : "instance_name.sictiam.dev.entrouvert.org",
66
      "combo_agent" : "agents-instance_name.sictiam.dev.entrouvert.org",
67
      "hobo" : "hobo-instance_name.sictiam.dev.entrouvert.org",
68
      "wcs" : "demarches-instance_name.sictiam.dev.entrouvert.org",
69
      "passerelle": "passerelle-instance_name.sictiam.dev.entrouvert.org",
70
      "fargo": "porte-documents-instance_name.sictiam.dev.entrouvert.org"
71
   }
72
}
hobo/contrib/ozwillo/scripts/create_user_ozwillo.py
1
import sys
2
import logging
3

  
4
from django.core.management import BaseCommand
5
from django.contrib.auth import get_user_model
6

  
7
from authentic2_auth_oidc.models import OIDCAccount
8
from authentic2_auth_oidc.models import OIDCProvider
9
from authentic2.a2_rbac.models import Role, OrganizationalUnit
10

  
11
args = sys.argv
12

  
13
try:
14
    email_address = args[1]
15
    username = args[2]
16
    user_id = args[3]
17
except IndexError:
18
    pass
19

  
20
#create agent_sve role
21
ou = OrganizationalUnit.objects.get(slug=u'default')
22
Role.objects.create(name=u'Agents SVE', ou=ou)
23

  
24
#create admin user from ozwillo in authentic
25
provider = OIDCProvider.objects.get(name='Ozwillo')
26

  
27
User = get_user_model()
28
new_user = User.objects.create(is_staff=True, is_superuser=True, username=username)
29

  
30
logging.info('Provisionning email %s with sub %s', email_address, user_id)
31
oidc_account, created = OIDCAccount.objects.select_related().get_or_create(provider=provider, sub=user_id, defaults={'user': new_user})
32

  
33
if created:
34
    if OIDCAccount.objects.filter(provider=provider, sub=user_id).count() > 1:
35
         oidc_account.delete()
36
         new_user.delete()
37
         logging.info('provisionned with uuid %s', new_user.msguuid)
38
    else:
39
        new_user.delete()
40
        new_user = oidc_account.user
41
if new_user.email != email_address:
42
    new_user.email = email_address
43
    new_user.save()
44
if new_user.ou != provider.ou:
45
    new_user.ou = provider.ou
46
    new_user.save()
47

  
hobo/contrib/ozwillo/urls.py
1
# Ozwillo plugin to deploy
2
# Copyright (C) 2017  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from django.conf.urls import patterns, url, include
18

  
19
from . import views
20

  
21

  
22
urlpatterns = patterns('',
23
    url(r'create-publik-instance', views.create_publik_instance, name='ozwillo-create-publik-instance'),
24
    url(r'delete-publik-instance', views.delete_publik_instance, name='ozwillo-delete-publik-instance'),
25
)
26

  
hobo/contrib/ozwillo/views.py
1
# Ozwillo plugin to deploy Publik
2
# Copyright (C) 2017  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
import os
17
import logging
18
import requests
19
import io
20
import json
21
import subprocess
22
import hmac
23
import tempfile
24
from hashlib import sha1
25

  
26
from django.views.decorators.csrf import csrf_exempt
27
from django.conf import settings
28
from django.http import HttpResponseForbidden, HttpResponseBadRequest, HttpResponseNotFound
29
from django.core.management import call_command
30

  
31

  
32
logger = logging.getLogger(__name__)
33

  
34
def valid_signature_required(func):
35
    signature_header_name = 'HTTP_X_HUB_SIGNATURE'
36
    api_secret = settings.OZWILLO_SECRET
37
    def wrapper(request):
38
        if signature_header_name in request.META:
39
             if request.META[signature_header_name].startswith('sha1='):
40
                 algo, received_hmac = request.META[signature_header_name].rsplit('=')
41
                 computed_hmac = hmac.new(api_secret, request.body, sha1).hexdigest()
42
                 # the received hmac is uppercase according to
43
                 # http://doc.ozwillo.com/#ref-3-2-1
44
                 if received_hmac.lower() != computed_hmac:
45
                     logger.error('Invalid HMAC')
46
                     return HttpResponseForbidden('Invalid HMAC')
47
             else:
48
                 logger.error('Invalid HMAC algo')
49
                 return HttpResponseForbidden('Invalid HMAC algo')
50
        else:
51
             logger.error('No HMAC in the header')
52
             return HttpResponseForbidden('No HMAC in the header')
53
        return func(request)
54
    return wrapper
55

  
56
def is_ozwillo_enabled(func):
57
    def wrapper(request):
58
        if not os.path.exists('/etc/hobo/ozwillo'):
59
            return HttpResponseNotFound('Owillo providing is not active here.')
60
        return func(request)
61
    return wrapper
62

  
63
@csrf_exempt
64
@is_ozwillo_enabled
65
@valid_signature_required
66
def create_publik_instance(request):
67
    data = json.loads(request.body)
68

  
69
    logger.debug(data)
70

  
71
    client_id = data.pop('client_id')
72
    client_secret = data.pop('client_secret')
73
    instance_id = data.pop('instance_id')
74
    instance_name = data.pop('organization_name', None)
75

  
76
    if not instance_name:
77
        return HttpResponseBadRequest('Missing parameter "instance_name"')
78

  
79
    instance_name = instance_name.lower()
80
    registration_uri = data.pop('instance_registration_uri')
81
    organization = data['organization']
82
    user = data['user']
83

  
84
    services = {'services': [{
85
                    'local_id': 'publik',
86
                    'name': 'Publik - %s' % (instance_name),
87
                    'service_uri':'https://connexion-%s.%s/accounts/oidc/login' % (instance_name, settings.OZWILLO_ENV_DOMAIN),
88
                    'description': 'Gestion de la relation usagers',
89
                    'tos_uri': 'https://publik.entrouvert.com/',
90
                    'policy_uri': 'https://publik.entrouvert.com/',
91
                    'icon': 'https://publik.entrouvert.com/static/img/logo-publik.png',
92
                    'payment_option': 'FREE',
93
                    'target_audience': ['PUBLIC_BODIES',
94
                                        'CITIZENS',
95
                                        'COMPANIES'],
96
                    'contacts': ['https://publik.entrouvert.com/'],
97
                    'redirect_uris':['https://connexion-%s.%s/accounts/oidc/callback' % (instance_name, settings.OZWILLO_ENV_DOMAIN)],
98
                }],
99
                'instance_id': instance_id,
100
                'destruction_uri': settings.OZWILLO_DESTRUCTION_URI,
101
                'destruction_secret': settings.OZWILLO_DESTRUCTION_SECRET,
102
                'needed_scopes': []
103
    }
104

  
105
    logger.debug(services)
106

  
107
    template_recipe = json.load(open('/etc/hobo/ozwillo/template_recipe.json', 'rb'))
108
    var = template_recipe['variables']
109
    for key, value in var.items():
110
        var[key] = value.replace('instance_name', instance_name)
111

  
112
    template_recipe['variables'] = var
113
    domain = var['combo']
114
    domain_agent = var['combo_agent']
115
    imp_site_template = json.load(open('/etc/hobo/ozwillo/import-site-template.json', 'r'))
116

  
117
    for row in imp_site_template:
118
        row['fields']['redirect_url'] = row['fields']['redirect_url'].replace('instance_name', instance_name)
119

  
120
    combo_file_handle, combo_file_path = tempfile.mkstemp()
121
    recipe_file_handle, recipe_file_path = tempfile.mkstemp()
122

  
123
    #set read rights for others as mkstemp create 600 file
124
    os.fchmod(combo_file_handle, 0644)
125

  
126
    with io.open(combo_file_path, 'w', encoding='utf-8') as f:
127
        f.write(unicode(json.dumps(imp_site_template, ensure_ascii=False)))
128

  
129
    with io.open(recipe_file_path, 'w', encoding='utf-8') as f:
130
        f.write(unicode(json.dumps(template_recipe, ensure_ascii=False)))
131

  
132
    call_command('cook', recipe_file_path)
133
    subprocess.call(['sudo', '-u', 'combo', 'combo-manage',
134
                     'tenant_command', 'import_site',
135
                     combo_file_path, '-d', domain])
136
    subprocess.call(['sudo', '-u', 'combo', 'combo-manage',
137
                     'tenant_command', 'import_site',
138
                     '/etc/hobo/ozwillo/import-site-agents.json', '-d', domain_agent])
139

  
140
    subprocess.call(['sudo', '-u', 'authentic-multitenant', 'authentic2-multitenant-manage', 'tenant_command',
141
                     'oidc-register-issuer', '-d', 'connexion-%s.%s' % (instance_name, settings.OZWILLO_ENV_DOMAIN),
142
                     '--scope', 'profile', '--scope', 'email', '--issuer', settings.OZWILLO_PLATEFORM, '--client-id', client_id,
143
                     '--client-secret', client_secret, '--claim-mapping', '"given_name first_name always_verified"',
144
                     '--claim-mapping', '"family_name last_name always_verified"', '--ou-slug', 'default', '--claim-mapping',
145
                     '"email email required"', 'Ozwillo'])
146
    subprocess.call(['sudo', '-u', 'authentic-multitenant',
147
                     'authentic2-multitenant-manage', 'tenant_command','runscript', '-d',
148
                     'connexion-%s.%s' % (instance_name, settings.OZWILLO_ENV_DOMAIN),
149
                     '/usr/lib/python2.7/dist-packages/hobo/contrib/ozwillo/scripts/create_user_ozwillo.py',
150
                     user['email_address'], user['name'], user['id']])
151
    os.remove(combo_file_path)
152
    os.remove(recipe_file_path)
153

  
154
    headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
155
    requests.post(registration_uri, data=json.dumps(services), auth=(client_id, client_secret), headers=headers)
156

  
157
@csrf_exempt
158
@is_ozwillo_enabled
159
@valid_signature_required
160
def delete_publik_instance(request):
161
    pass
162

  
hobo/urls.py
32 32
    url(r'^login/local/$', login_local), # to be used as backup, in case of idp down
33 33
    url(r'^accounts/mellon/', include('mellon.urls')),
34 34
)
35
if 'hobo.contrib.ozwillo' in settings.INSTALLED_APPS:
36
    urlpatterns += patterns('',
37
                           url(r'ozwillo/', include('hobo.contrib.ozwillo.urls')),
38
                          )
35
-