Projet

Général

Profil

Télécharger (7,63 ko) Statistiques
| Branche: | Tag: | Révision:

organization_api / ckanext / ozwillo_organization_api / plugin.py @ b34cfce8

1
from hashlib import sha1
2
import hmac
3
import requests
4
import logging
5
import json
6
from slugify import slugify
7

    
8
import ckan.plugins as plugins
9
import ckan.plugins.toolkit as toolkit
10

    
11
import ckan.logic as logic
12
import ckan.lib.base as base
13

    
14
from pylons import config
15
from ckan.common import request, _
16
from ckan.logic.action.create import _group_or_org_create as group_or_org_create
17
from ckan.logic.action.create import user_create
18
from ckan.logic.action.delete import _group_or_org_purge
19
from ckan.lib.plugins import DefaultOrganizationForm
20

    
21
plugin_config_prefix = 'ckanext.ozwillo_organization_api.'
22

    
23
log = logging.getLogger(__name__)
24

    
25
def valid_signature_required(func):
26

    
27
    signature_header_name = config.get(plugin_config_prefix + 'signature_header_name',
28
                                       'X-Hub-Signature')
29
    instantiated_secret = config.get(plugin_config_prefix + 'instantiation_secret',
30
                                     'secret')
31

    
32
    def wrapper(context, data):
33
        if signature_header_name in request.headers:
34
            if request.headers[signature_header_name].startswith('sha1='):
35
                algo, received_hmac = request.headers[signature_header_name].rsplit('=')
36
                computed_hmac = hmac.new(instantiated_secret, request.body, sha1).hexdigest()
37
                # the received hmac is uppercase according to
38
                # http://doc.ozwillo.com/#ref-3-2-1
39
                if received_hmac != computed_hmac.upper():
40
                    raise logic.NotAuthorized(_('Invalid HMAC'))
41
            else:
42
                raise logic.ValidationError(_('Invalid HMAC algo'))
43
        else:
44
            raise logic.NotAuthorized(_("No HMAC in the header"))
45
        return func(context, data)
46
    return wrapper
47

    
48
@valid_signature_required
49
def create_organization(context, data_dict):
50
    context['ignore_auth'] = True
51
    model = context['model']
52
    session = context['session']
53

    
54
    destruction_secret = config.get(plugin_config_prefix + 'destruction_secret',
55
                                       'changeme')
56

    
57
    client_id = data_dict.pop('client_id')
58
    client_secret = data_dict.pop('client_secret')
59
    instance_id = data_dict.pop('instance_id')
60

    
61
    # re-mapping received dict
62
    registration_uri = data_dict.pop('instance_registration_uri')
63
    organization = data_dict['organization']
64
    user = data_dict['user']
65
    user_dict = {
66
        'id': user['id'],
67
        'name': user['id'].replace('-', ''),
68
        'email': user['email_address'],
69
        'password': user['id']
70
    }
71
    user_obj = model.User.get(user_dict['name'])
72

    
73
    org_dict = {
74
        'type': 'organization',
75
        'name': slugify(organization['name']),
76
        'id': instance_id,
77
        'title': organization['name'],
78
        'user': user_dict['name']
79
    }
80

    
81
    if not user_obj:
82
        user_create(context, user_dict)
83
    context['user'] = user_dict['name']
84

    
85
    try:
86
        delete_uri = toolkit.url_for(host=request.host,
87
                                     controller='api', action='action',
88
                                     logic_function="delete-ozwillo-organization",
89
                                     ver=context['api_version'],
90
                                     qualified=True)
91
        organization_uri = toolkit.url_for(host=request.host,
92
                                           controller='organization',
93
                                           action='read',
94
                                           id=org_dict['name'],
95
                                           qualified=True)
96
        default_icon_url = toolkit.url_for(host=request.host,
97
                                           qualified=True,
98
                                           controller='home',
99
                                           action='index') + 'opendata.png'
100

    
101
        group_or_org_create(context, org_dict, is_org=True)
102

    
103
        # setting organization as active explicitely
104
        group = model.Group.get(org_dict['name'])
105
        group.state = 'active'
106
        group.image_url = default_icon_url
107
        group.save()
108
        model.repo.new_revision()
109
        model.GroupExtra(group_id=group.id, key='client_id',
110
                         value=client_id).save()
111
        model.GroupExtra(group_id=group.id, key='client_secret',
112
                         value=client_secret).save()
113
        session.flush()
114

    
115
        # notify about organization creation
116
        services = {'services': [{
117
            'local_id': 'organization',
118
            'name': 'Open Data',
119
            'service_uri': organization_uri + '/sso',
120
            'description': 'Organization ' + org_dict['name'] + ' on CKAN',
121
            'tos_uri': organization_uri,
122
            'policy_uri': organization_uri,
123
            'icon': group.image_url,
124
            'payment_option': 'FREE',
125
            'target_audience': ['PUBLIC_BODIES'],
126
            'contacts': [organization_uri],
127
            'redirect_uris': [organization_uri + '/callback'],
128
            'post_logout_redirect_uris': [organization_uri + '/logout'],
129
            'visible': False}],
130
            'instance_id': instance_id,
131
            'destruction_uri': delete_uri,
132
            'destruction_secret': destruction_secret,
133
            'needed_scopes': [{
134
                'scope_id': 'profile',
135
                'motivation': 'Used to link user to the organization'
136
            }]
137
        }
138
        headers = {'Content-type': 'application/json',
139
                   'Accept': 'application/json'}
140
        requests.post(registration_uri,
141
                      data=json.dumps(services),
142
                      auth=(client_id, client_secret),
143
                      headers=headers
144
                  )
145
    except logic.ValidationError, e:
146
        log.debug('Validation error "%s" occured while creating organization' % e)
147
        raise
148

    
149
@valid_signature_required
150
def delete_organization(context, data_dict):
151
    data_dict['id'] = data_dict.pop('instance_id')
152
    context['ignore_auth'] = True
153
    _group_or_org_purge(context, data_dict, is_org=True)
154

    
155

    
156
class OrganizationForm(plugins.SingletonPlugin, DefaultOrganizationForm):
157
    """
158
    Custom form ignoring 'title' and 'name' organization fields
159
    """
160
    plugins.implements(plugins.IGroupForm)
161

    
162
    def is_fallback(self):
163
        return True
164

    
165
    def group_types(self):
166
        return ('organization',)
167

    
168
    def form_to_db_schema(self):
169
        schema = super(OrganizationForm, self).form_to_db_schema()
170
        del schema['name']
171
        del schema['title']
172
        return schema
173

    
174

    
175
class ErrorController(base.BaseController):
176
    def error403(self):
177
        return base.abort(403, '')
178

    
179

    
180
class OzwilloOrganizationApiPlugin(plugins.SingletonPlugin):
181
    """
182
    API for OASIS to create and delete an organization
183
    """
184
    plugins.implements(plugins.IActions)
185
    plugins.implements(plugins.IConfigurer)
186
    plugins.implements(plugins.IRoutes)
187

    
188
    def before_map(self, map):
189
        # disable organization and members api
190
        for action in ('member_create', 'member_delete',
191
                       'organization_member_delete',
192
                       'organization_member_create',
193
                       'organization_create',
194
                       'organization_update',
195
                       'organization_delete'):
196
            map.connect('/api/{ver:.*}/action/%s' % action,
197
                        controller=__name__ + ':ErrorController',
198
                        action='error403')
199
        return map
200

    
201
    def after_map(self, map):
202
        return map
203

    
204
    def update_config(self, config):
205
        toolkit.add_template_directory(config, 'templates')
206
        toolkit.add_public_directory(config, 'public')
207

    
208
    def get_actions(self):
209
        return {
210
            'create-ozwillo-organization': create_organization,
211
            'delete-ozwillo-organization': delete_organization
212
        }
(2-2/2)