def post(self, request, email, path): """Emails login url with secret token to verify email ownership. Token is signed (but not encrypted) and valid only for the current site. The login url contains a path to which the user should be redirected after successful verification. """ if email == None: return http.HttpResponseBadRequest('Email not set.') if not models.is_email_valid(email): return http.HttpResponseBadRequest('Email has invalid format.') if path is None or not url_utils.validate_redirection_target(path): path = '/' token = login_token.generate_login_token(request.site, site_url=request.site_url, email=email) params = urllib.urlencode(dict(next=path, token=token)) url = '{0}{1}?{2}'.format(request.site_url, reverse('login'), params) subject = '[{0}] email verification'.format(request.site_url) from_email = settings.TOKEN_EMAIL_FROM body = ( 'Follow the link to verify your email address\n' + '{0}\n'.format(url) + '\n' + 'Ignore this email if you have not requested such verification.') send_mail(subject, body, from_email, [email], fail_silently=False) return http.HttpResponseNoContent()
def post(self, request, assertion): """Logs a user in (establishes a session cookie). Verifies BrowserID assertion and check that a user with an email verified by the BrowserID is known (added to users list). """ if assertion == None: return http.HttpResponseBadRequest('BrowserId assertion not set.') try: user = auth.authenticate(site=request.site, site_url=request.site_url, assertion=assertion) except AssertionVerificationException as ex: logger.debug('Assertion verification failed.') return http.HttpResponseBadRequest(str(ex)) if user is not None: auth.login(request, user) # Store all user data needed by Auth view in session, this # way, user table does not need to be queried during the # performance critical request (sessions are cached). request.session['user_id'] = user.id logger.debug('%s successfully logged.' % (user.email)) return http.HttpResponseNoContent() else: # Unkown user. # Return not authorized because request was well formed (400 # doesn't seem appropriate). return http.HttpResponseNotAuthorized()
def post(self, request, email, path): """If the email owner can access the site, sends the login token. Token is not sent if the email owner is not allowed to access the site in order to avoid abusing this end point to send a flood of emails to unknown addresses. Token is signed (but not encrypted) and valid only for the current site. The login url contains a path to which the user should be redirected after successful verification. """ if email == None: return http.HttpResponseBadRequest('Email not set.') if not models.is_email_valid(email): return http.HttpResponseBadRequest('Email has invalid format.') if path is None or not url_utils.validate_redirection_target(path): path = '/' if request.site.users.find_item_by_email(email) is None: # The email owner can not access the site. The token is # not sent, but the response is identical to the response # returned when the token is sent. This way it is not # possible to use the login form to query which emails are # allowed access. Such queries can still be possible by # response timing. return http.HttpResponseNoContent() token = login_token.generate_login_token(request.site, site_url=request.site_url, email=email) params = urllib.urlencode(dict(next=path, token=token)) url = '{0}{1}?{2}'.format(request.site_url, reverse('login'), params) subject = '{0} access token'.format(request.site_url) body = ( 'Hello,\n\n' 'You have requested access to {0}.\n'.format(request.site_url) + 'Open this link to verify your email address:\n\n' '{0}\n\n'.format(url) + 'If you have not requested such access, please ignore this email.\n' 'The link is valid for the next 30 minutes and can be used once.\n' ) from_email = settings.TOKEN_EMAIL_FROM success = False try: success = (send_mail( subject, body, from_email, [email], fail_silently=False) > 0) except Exception as ex: logger.warning(ex) if not success: # This probaly can be also due to invalid email address, # in these cases 400 would be better. msg = 'Email delivery problem. ' \ 'Check the entered address or try again in a few minutes.' return http.HttpResponseInternalError(msg) return http.HttpResponseNoContent()
def process_request(self, request): url = request.META.get('HTTP_SITE_URL', None) if url is None: return http.HttpResponseBadRequest('Missing Site-Url header') url = url_utils.remove_default_port(url) parts = url.split('://', 1) if len(parts) != 2: return http.HttpResponseBadRequest('Site-Url has incorrect format') scheme, host = parts if not self._alias_defined(request.site, url): return self._site_url_invalid(request, scheme, host) request.site_url = url request.META[SECURE_PROXY_SSL_HEADER] = scheme request.META['HTTP_X_FORWARDED_HOST'] = host # TODO: use is_secure() instead request.https = (scheme == 'https') return None
def _site_url_invalid(self, request, scheme, host): if self._needs_https_redirect(request.site, scheme, host): logger.debug('Request over http, redirecting to https') return redirect('https://' + host + self._get_full_path(request)) msg = 'Invalid request URL, you can use wwwhisper admin to allow ' \ 'requests from this address.' logger.warning(msg) return http.HttpResponseBadRequest(msg)
def put(self, request, title, header, message, branding): try: request.site.update_skin(title=title, header=header, message=message, branding=branding) except ValidationError as ex: return http.HttpResponseBadRequest( 'Failed to update login page: ' + ', '.join(ex.messages)) return http.HttpResponseOKJson(request.site.skin())
def get(self, request): """Logs a user in (establishes a session cookie). Verifies a token and check that a user with an email encoded in the token is known. On success redirects to path passed in the 'next' url argument. """ # TODO(jw): should this first check if the user is already # logged in and redirect to '/' if this is the case? token = request.GET.get('token') if token == None: return http.HttpResponseBadRequest('Token missing.') try: user = auth.authenticate(site=request.site, site_url=request.site_url, token=token) except AuthenticationError as ex: logger.debug('Token verification failed.') return http.HttpResponseBadRequest(str(ex)) if user is not None: auth.login(request, user) user.login_successful() # Store all user data needed by Auth view in session, this # way, user table does not need to be queried during the # performance critical request (sessions are cached). request.session['user_id'] = user.id logger.debug('%s successfully logged.' % (user.email)) redirect_to = request.GET.get('next') if (redirect_to is None or not url_utils.validate_redirection_target(redirect_to)): redirect_to = '/' return http.HttpResponseRedirect(request.site_url + redirect_to) # Return not authorized because request was well formed (400 # doesn't seem appropriate). return http.HttpResponseNotAuthorized( _html_or_none(request, 'nothing_accessible.html'))
def post(self, request, **kwargs): """Ads a new resource to the collection. Args: **kwargs: holds collection dependent arguments that are used to create the resource. Returns json representation of the added resource.""" try: created_item = self.collection.create_item(**kwargs) except ValidationError as ex: # ex.messages is a list of errors. return http.HttpResponseBadRequest(', '.join(ex.messages)) except LimitExceeded as ex: return http.HttpResponseLimitExceeded(str(ex)) attributes_dict = created_item.attributes_dict(request.site_url) response = http.HttpResponseCreated(attributes_dict) response['Location'] = attributes_dict['self'] response['Content-Location'] = attributes_dict['self'] return response
def get(self, request): """Invoked by the HTTP server with a single path argument. The HTTP server should pass the path argument verbatim, without any transformations or decoding. Access control mechanism should work on user visible paths, not paths after internal rewrites performed by the server. At the moment, the path is allowed to contain a query part, which is ignored (this is because nginx does not expose encoded path without the query part). The method follows be conservative in what you accept principle. The path should be absolute and normalized, without fragment id, otherwise access is denied. Browsers in normal operations perform path normalization and do not send fragment id. Multiple consecutive '/' separators are permitted, because these are not normalized by browsers, and are used by legitimate applications. Paths with '/./' and '/../', should not be normally sent by browsers and can be a sign of something suspicious happening. It is extremely important that wwwhisper does not perform any path transformations that are not be compatible with transformations done by the HTTP server. """ encoded_path = self._extract_encoded_path_argument(request) if encoded_path is None: return http.HttpResponseBadRequest( "Auth request should have 'path' argument.") # Do not allow requests that contain the 'User' header. The # header is passed to backends and must be guaranteed to be # set by wwwhisper. # This check should already be performed by HTTP server. if 'HTTP_USER' in request.META: return http.HttpResponseBadRequest( "Client can not set the 'User' header") debug_msg = "Auth request to '%s'" % (encoded_path) path_validation_error = None if url_utils.contains_fragment(encoded_path): path_validation_error = "Path should not include fragment ('#')" else: stripped_path = url_utils.strip_query(encoded_path) decoded_path = url_utils.decode(stripped_path) decoded_path = url_utils.collapse_slashes(decoded_path) if not url_utils.is_canonical(decoded_path): path_validation_error = 'Path should be absolute and ' \ 'normalized (starting with / without /../ or /./ or //).' if path_validation_error is not None: logger.debug('%s: incorrect path.' % (debug_msg)) return http.HttpResponseBadRequest(path_validation_error) user = _get_user(request) location = request.site.locations.find_location(decoded_path) if user is not None: debug_msg += " by '%s'" % (user.email) respone = None if location is not None and location.can_access(user): logger.debug('%s: access granted.' % (debug_msg)) response = http.HttpResponseOK('Access granted.') else: logger.debug('%s: access denied.' % (debug_msg)) response = http.HttpResponseNotAuthorized( _html_or_none(request, 'not_authorized.html', {'email': user.email})) response['User'] = user.email return response if (location is not None and location.open_access_granted() and not location.open_access_requires_login()): logger.debug('%s: authentication not required, access granted.' % (debug_msg)) return http.HttpResponseOK('Access granted.') logger.debug('%s: user not authenticated.' % (debug_msg)) return http.HttpResponseNotAuthenticated( _html_or_none(request, 'login.html', request.site.skin()))