Projet

Général

Profil

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

organization_api / ckanext / ozwillo_organization_api / plugin.py @ master

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(secret_prefix):
26

    
27
    signature_header_name = config.get(plugin_config_prefix + 'signature_header_name',
28
                                       'X-Hub-Signature')
29
    api_secret = config.get(plugin_config_prefix + secret_prefix +'_secret', 'secret')
30

    
31
    def decorator(func):
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(api_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.lower() != computed_hmac:
40
                        log.info('Invalid HMAC')
41
                        raise logic.NotAuthorized(_('Invalid HMAC'))
42
                else:
43
                    log.info('Invalid HMAC algo')
44
                    raise logic.ValidationError(_('Invalid HMAC algo'))
45
            else:
46
                log.info('No HMAC in the header')
47
                raise logic.NotAuthorized(_("No HMAC in the header"))
48
            return func(context, data)
49
        return wrapper
50
    return decorator
51

    
52

    
53
@valid_signature_required(secret_prefix='instantiation')
54
def create_organization(context, data_dict):
55
    context['ignore_auth'] = True
56
    model = context['model']
57
    session = context['session']
58

    
59
    destruction_secret = config.get(plugin_config_prefix + 'destruction_secret',
60
                                       'changeme')
61

    
62
    client_id = data_dict.pop('client_id')
63
    client_secret = data_dict.pop('client_secret')
64
    instance_id = data_dict.pop('instance_id')
65

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

    
78
    org_dict = {
79
        'type': 'organization',
80
        'name': slugify(organization['name']),
81
        'id': instance_id,
82
        'title': organization['name'],
83
        'user': user_dict['name']
84
    }
85

    
86
    if not user_obj:
87
        user_create(context, user_dict)
88
    context['user'] = user_dict['name']
89

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

    
106
        group_or_org_create(context, org_dict, is_org=True)
107

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

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

    
154
@valid_signature_required(secret_prefix='destruction')
155
def delete_organization(context, data_dict):
156
    data_dict['id'] = data_dict.pop('instance_id')
157
    context['ignore_auth'] = True
158
    _group_or_org_purge(context, data_dict, is_org=True)
159

    
160

    
161
class OrganizationForm(plugins.SingletonPlugin, DefaultOrganizationForm):
162
    """
163
    Custom form ignoring 'title' and 'name' organization fields
164
    """
165
    plugins.implements(plugins.IGroupForm)
166

    
167
    def is_fallback(self):
168
        return False
169

    
170
    def group_types(self):
171
        return ('organization',)
172

    
173
    def group_controller(self):
174
        return 'organization'
175

    
176
    def form_to_db_schema(self):
177
        schema = super(OrganizationForm, self).form_to_db_schema()
178
        del schema['name']
179
        del schema['title']
180
        return schema
181

    
182

    
183
class ErrorController(base.BaseController):
184
    def error403(self):
185
        return base.abort(403, '')
186

    
187

    
188
class OzwilloOrganizationApiPlugin(plugins.SingletonPlugin):
189
    """
190
    API for OASIS to create and delete an organization
191
    """
192
    plugins.implements(plugins.IActions)
193
    plugins.implements(plugins.IConfigurer)
194
    plugins.implements(plugins.IRoutes)
195

    
196
    def before_map(self, map):
197
        # disable organization and members api
198
        for action in ('member_create', 'member_delete',
199
                       'organization_member_delete',
200
                       'organization_member_create',
201
                       'organization_create',
202
                       'organization_update',
203
                       'organization_delete'):
204
            map.connect('/api/{ver:.*}/action/%s' % action,
205
                        controller=__name__ + ':ErrorController',
206
                        action='error403')
207
        return map
208

    
209
    def after_map(self, map):
210
        return map
211

    
212
    def update_config(self, config):
213
        toolkit.add_template_directory(config, 'templates')
214
        toolkit.add_public_directory(config, 'public')
215

    
216
    def get_actions(self):
217
        return {
218
            'create-ozwillo-organization': create_organization,
219
            'delete-ozwillo-organization': delete_organization
220
        }
(2-2/2)