2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
21 from django
.shortcuts
import render
22 from django
.http
import HttpResponseRedirect
23 from django
.contrib
.auth
import authenticate
24 from django
.contrib
.auth
.decorators
import login_required
25 from django
.contrib
import messages
26 from django
.contrib
.sites
.models
import RequestSite
27 from django
.conf
import settings
28 from django
.utils
.translation
import ugettext
as _
29 from django
.views
.decorators
.cache
import never_cache
30 from django
.views
.generic
.base
import View
, TemplateView
31 from django
.utils
.decorators
import method_decorator
32 from django
.core
.urlresolvers
import reverse
33 from django
.utils
.http
import is_safe_url
35 from oauth2client
.client
import FlowExchangeError
37 from mygpo
.db
.couchdb
.user
import user_by_google_email
, set_users_google_email
38 from mygpo
.decorators
import allowed_methods
, repeat_on_conflict
39 from mygpo
.web
.forms
import RestorePasswordForm
40 from mygpo
.users
.models
import User
41 from mygpo
.web
.forms
import ResendActivationForm
42 from mygpo
.constants
import DEFAULT_LOGIN_REDIRECT
43 from mygpo
.web
.auth
import get_google_oauth_flow
46 logger
= logging
.getLogger(__name__
)
49 @repeat_on_conflict(['user'])
50 def login(request
, user
):
51 from django
.contrib
.auth
import login
55 class LoginView(View
):
56 """ View to login a user """
58 def get(self
, request
):
59 """ Shows the login page """
61 # Do not show login page for already-logged-in users
62 if request
.user
.is_authenticated():
63 return HttpResponseRedirect(DEFAULT_LOGIN_REDIRECT
)
65 return render(request
, 'login.html', {
66 'url': RequestSite(request
),
67 'next': request
.GET
.get('next', ''),
71 @method_decorator(never_cache
)
72 def post(self
, request
):
73 """ Carries out the login, redirects to get if it fails """
75 # redirect target on successful login
76 next_page
= request
.POST
.get('next', '')
78 # redirect target on failed login
79 login_page
= '{page}?next={next_page}'.format(page
=reverse('login'),
83 username
= request
.POST
.get('user', None)
85 messages
.error(request
, _('Username missing'))
86 return HttpResponseRedirect(login_page
)
88 password
= request
.POST
.get('pwd', None)
90 messages
.error(request
, _('Password missing'))
91 return HttpResponseRedirect(login_page
)
93 # find the user from the configured login systems, and verify pwd
94 user
= authenticate(username
=username
, password
=password
)
97 messages
.error(request
, _('Wrong username or password.'))
98 return HttpResponseRedirect(login_page
)
101 if not user
.is_active
:
102 # if the user is not active, find the reason
104 messages
.error(request
, _('You have deleted your account, '
105 'but you can register again'))
106 return HttpResponseRedirect(login_page
)
109 messages
.error(request
,
110 _('Please activate your account first.'))
111 return HttpResponseRedirect(login_page
)
113 # set up the user's session
117 if is_safe_url(next_page
):
118 return HttpResponseRedirect(next_page
)
121 # TODO: log a warning that next_page is not
122 # considered a safe redirect target
125 return HttpResponseRedirect(DEFAULT_LOGIN_REDIRECT
)
128 def get_user(username
, email
, is_active
=None):
130 return User
.get_user(username
, is_active
=None)
133 return User
.get_user_by_email(email
, is_active
=None)
139 def restore_password(request
):
141 if request
.method
== 'GET':
142 form
= RestorePasswordForm()
143 return render(request
, 'restore_password.html', {
148 form
= RestorePasswordForm(request
.POST
)
149 if not form
.is_valid():
150 return HttpResponseRedirect('/login/')
152 user
= get_user(form
.cleaned_data
['username'], form
.cleaned_data
['email'], is_active
=None)
155 messages
.error(request
, _('User does not exist.'))
157 return render(request
, 'password_reset_failed.html')
159 site
= RequestSite(request
)
160 pwd
= "".join(random
.sample(string
.letters
+string
.digits
, 8))
161 subject
= _('Reset password for your account on %s') % site
162 message
= _('Here is your new password for your account %(username)s on %(site)s: %(password)s') % {'username': user
.username
, 'site': site
, 'password': pwd
}
163 user
.email_user(subject
, message
, settings
.DEFAULT_FROM_EMAIL
)
164 _set_password(user
, pwd
)
165 return render(request
, 'password_reset.html')
168 @repeat_on_conflict(['user'])
169 def _set_password(user
, password
):
170 user
.set_password(password
)
174 @repeat_on_conflict(['user'])
175 def _set_active(user
, is_active
=True):
176 user
.is_active
= is_active
181 @allowed_methods(['GET', 'POST'])
182 def resend_activation(request
):
184 if request
.method
== 'GET':
185 form
= ResendActivationForm()
186 return render(request
, 'registration/resend_activation.html', {
190 site
= RequestSite(request
)
191 form
= ResendActivationForm(request
.POST
)
194 if not form
.is_valid():
195 raise ValueError(_('Invalid Username entered'))
197 user
= get_user(form
.cleaned_data
['username'], form
.cleaned_data
['email'], is_active
=None)
199 raise ValueError(_('User does not exist.'))
202 raise ValueError(_('You have deleted your account, but you can register again.'))
204 if user
.activation_key
is None:
205 _set_active(user
=user
, is_active
=True)
206 raise ValueError(_('Your account already has been activated. Go ahead and log in.'))
208 elif user
.activation_key_expired():
209 raise ValueError(_('Your activation key has expired. Please try another username, or retry with the same one tomorrow.'))
211 except ValueError, e
:
212 messages
.error(request
, unicode(e
))
214 return render(request
, 'registration/resend_activation.html', {
220 user
.send_activation_email(site
)
222 except AttributeError:
223 user
.send_activation_email(site
)
225 return render(request
, 'registration/resent_activation.html')
229 class GoogleLogin(View
):
230 """ Redirects to Google Authentication page """
232 def get(self
, request
):
233 flow
= get_google_oauth_flow(request
)
234 auth_uri
= flow
.step1_get_authorize_url()
235 return HttpResponseRedirect(auth_uri
)
238 class GoogleLoginCallback(TemplateView
):
239 """ Logs user in, or connects user to account """
241 def get(self
, request
):
243 if request
.GET
.get('error'):
244 messages
.error(request
, _('Login failed.'))
245 return HttpResponseRedirect(reverse('login'))
247 code
= request
.GET
.get('code')
249 messages
.error(request
, _('Login failed.'))
250 return HttpResponseRedirect(reverse('login'))
252 flow
= get_google_oauth_flow(request
)
255 credentials
= flow
.step2_exchange(code
)
256 except FlowExchangeError
:
257 messages
.error(request
, _('Login with Google is currently not possible.'))
258 logger
.exception('Login with Google failed')
259 return HttpResponseRedirect(reverse('login'))
261 email
= credentials
.token_response
['id_token']['email']
264 if request
.user
.is_authenticated():
265 set_users_google_email(request
.user
, email
)
266 messages
.success(request
, _('Your account has been connected with '
267 '{google}. Open Settings to change this.'.format(
269 return HttpResponseRedirect(DEFAULT_LOGIN_REDIRECT
)
271 # Check if Google account is connected
272 user
= user_by_google_email(email
)
276 messages
.error(request
, _('No account connected with your Google '
277 'account %s. Please log in to connect.' % email
))
278 return HttpResponseRedirect('{login}?next={connect}'.format(
279 login
=reverse('login'), connect
=reverse('login-google')))
283 return HttpResponseRedirect(DEFAULT_LOGIN_REDIRECT
)