cb8ef0888b744069781e230d7d0f632187fb3800
[astakos] / snf-astakos-app / astakos / im / auth_providers.py
1 # Copyright 2011 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
11 #   2. Redistributions in binary form must reproduce the above
12 #      copyright notice, this list of conditions and the following
13 #      disclaimer in the documentation and/or other materials
14 #      provided with the distribution.
15 #
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28 #
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
33
34
35 from django.core.urlresolvers import reverse
36 from django.utils.translation import ugettext as _
37 from django.utils.datastructures import SortedDict
38
39 from django.conf import settings
40
41 from astakos.im import settings as astakos_settings
42 from astakos.im import messages as astakos_messages
43
44 import logging
45
46 logger = logging.getLogger(__name__)
47
48 # providers registry
49 PROVIDERS = {}
50
51 class AuthProviderBase(type):
52
53     def __new__(cls, name, bases, dct):
54         include = False
55         if [b for b in bases if isinstance(b, AuthProviderBase)]:
56             type_id = dct.get('module')
57             if type_id:
58                 include = True
59             if type_id in astakos_settings.IM_MODULES:
60                 dct['module_enabled'] = True
61
62         newcls = super(AuthProviderBase, cls).__new__(cls, name, bases, dct)
63         if include:
64             PROVIDERS[type_id] = newcls
65         return newcls
66
67
68 class AuthProvider(object):
69
70     __metaclass__ = AuthProviderBase
71
72     module = None
73     module_active = False
74     module_enabled = False
75     one_per_user = False
76     login_prompt = _('Login using ')
77     primary_login_prompt = _('Login using ')
78
79     def get_message(self, msg, **kwargs):
80         params = kwargs
81         params.update({'provider': self.get_title_display})
82
83         override_msg = getattr(self, 'get_%s_message_display' % msg.lower(), None)
84         msg = 'AUTH_PROVIDER_%s' % msg
85         return override_msg or getattr(astakos_messages, msg, msg) % params
86
87     def __init__(self, user=None):
88         self.user = user
89
90     def __getattr__(self, key):
91         if not key.startswith('get_'):
92             return super(AuthProvider, self).__getattribute__(key)
93
94         if key.endswith('_display') or key.endswith('template'):
95             attr = key.replace('_display', '').replace('get_','')
96             settings_attr = self.get_setting(attr.upper())
97             if not settings_attr:
98                 return getattr(self, attr)
99             return _(settings_attr)
100         else:
101             return super(AuthProvider, self).__getattr__(key)
102
103     def get_setting(self, name, default=None):
104         attr = 'ASTAKOS_AUTH_PROVIDER_%s_%s' % (self.module.upper(), name.upper())
105         attr_sec = 'ASTAKOS_%s_%s' % (self.module.upper(), name.upper())
106         if not hasattr(settings, attr):
107             return getattr(settings, attr_sec, default)
108         return getattr(settings, attr, default)
109
110     def is_available_for_login(self):
111         """ A user can login using authentication provider"""
112         return self.is_active() and self.get_setting('CAN_LOGIN',
113                                                      self.is_active())
114
115     def is_available_for_create(self):
116         """ A user can create an account using this provider"""
117         return self.is_active() and self.get_setting('CAN_CREATE',
118                                                    self.is_active())
119
120     def is_available_for_add(self):
121         """ A user can assign provider authentication method"""
122         return self.is_active() and self.get_setting('CAN_ADD',
123                                                    self.is_active())
124
125     def is_active(self):
126         return self.module in astakos_settings.IM_MODULES
127
128
129 class LocalAuthProvider(AuthProvider):
130     module = 'local'
131     title = _('Local password')
132     description = _('Create a local password for your account')
133     add_prompt =  _('Create a local password for your account')
134     login_prompt = _('if you already have a username and password')
135     signup_prompt = _('New to ~okeanos ?')
136     signup_link_prompt = _('create an account now')
137
138
139     @property
140     def add_url(self):
141         return reverse('password_change')
142
143     one_per_user = True
144
145     login_template = 'im/auth/local_login_form.html'
146     login_prompt_template = 'im/auth/local_login_prompt.html'
147     signup_prompt_template = 'im/auth/local_signup_prompt.html'
148     details_tpl = _('You can login to your account using your'
149                     ' %(auth_backend)s password.')
150
151     @property
152     def extra_actions(self):
153         return [(_('Change password'), reverse('password_change')), ]
154
155
156 class ShibbolethAuthProvider(AuthProvider):
157     module = 'shibboleth'
158     title = _('Academic credentials (Shibboleth)')
159     add_prompt = _('Allows you to login to your account using your academic '
160                     'account')
161     details_tpl = _('Shibboleth account \'%(identifier)s\' is connected to your '
162                     ' account.')
163     user_title = _('Academic credentials (%(identifier)s)')
164     primary_login_prompt = _('If you are a student/researcher/faculty you can'
165                              ' login using your university-credentials in'
166                              ' the following page')
167
168     @property
169     def add_url(self):
170         return reverse('astakos.im.target.shibboleth.login')
171
172     login_template = 'im/auth/shibboleth_login.html'
173     login_prompt_template = 'im/auth/shibboleth_login_prompt.html'
174
175
176 class TwitterAuthProvider(AuthProvider):
177     module = 'twitter'
178     title = _('Twitter')
179     add_prompt = _('Allows you to login to your account using Twitter')
180     details_tpl = _('Twitter screen name: %(info_screen_name)s')
181     user_title = _('Twitter (%(info_screen_name)s)')
182
183     @property
184     def add_url(self):
185         return reverse('astakos.im.target.twitter.login')
186
187     login_template = 'im/auth/third_party_provider_generic_login.html'
188     login_prompt_template = 'im/auth/third_party_provider_generic_login_prompt.html'
189
190
191 class GoogleAuthProvider(AuthProvider):
192     module = 'google'
193     title = _('Google')
194     add_prompt = _('Allows you to login to your account using Google')
195     details_tpl = _('Google account: %(info_email)s')
196     user_title = _('Google (%(info_email)s)')
197
198     @property
199     def add_url(self):
200         return reverse('astakos.im.target.google.login')
201
202     login_template = 'im/auth/third_party_provider_generic_login.html'
203     login_prompt_template = 'im/auth/third_party_provider_generic_login_prompt.html'
204
205
206 class LinkedInAuthProvider(AuthProvider):
207     module = 'linkedin'
208     title = _('LinkedIn')
209     add_prompt = _('Allows you to login to your account using LinkedIn')
210     user_title = _('LinkedIn (%(info_emailAddress)s)')
211     details_tpl = _('LinkedIn account: %(info_emailAddress)s')
212
213     @property
214     def add_url(self):
215         return reverse('astakos.im.target.linkedin.login')
216
217     login_template = 'im/auth/third_party_provider_generic_login.html'
218     login_prompt_template = 'im/auth/third_party_provider_generic_login_prompt.html'
219
220
221 def get_provider(id, user_obj=None, default=None):
222     """
223     Return a provider instance from the auth providers registry.
224     """
225     return PROVIDERS.get(id, default)(user_obj)
226