Add the concept of require_fresh_login to both the backend and frontend. Sensitive methods will now be marked with the annotation, which requires that the user has performed a login within 10 minutes or they are asked to do so in the UI before running the operation again.
This commit is contained in:
parent
1e7e012b92
commit
e783df31e0
9 changed files with 174 additions and 61 deletions
|
@ -1,7 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
from flask import Blueprint, request, make_response, jsonify
|
from flask import Blueprint, request, make_response, jsonify, session
|
||||||
from flask.ext.restful import Resource, abort, Api, reqparse
|
from flask.ext.restful import Resource, abort, Api, reqparse
|
||||||
from flask.ext.restful.utils.cors import crossdomain
|
from flask.ext.restful.utils.cors import crossdomain
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
|
@ -66,6 +67,11 @@ class Unauthorized(ApiException):
|
||||||
ApiException.__init__(self, 'insufficient_scope', 403, 'Unauthorized', payload)
|
ApiException.__init__(self, 'insufficient_scope', 403, 'Unauthorized', payload)
|
||||||
|
|
||||||
|
|
||||||
|
class FreshLoginRequired(ApiException):
|
||||||
|
def __init__(self, payload=None):
|
||||||
|
ApiException.__init__(self, 'fresh_login_required', 401, "Requires fresh login", payload)
|
||||||
|
|
||||||
|
|
||||||
class ExceedsLicenseException(ApiException):
|
class ExceedsLicenseException(ApiException):
|
||||||
def __init__(self, payload=None):
|
def __init__(self, payload=None):
|
||||||
ApiException.__init__(self, None, 402, 'Payment Required', payload)
|
ApiException.__init__(self, None, 402, 'Payment Required', payload)
|
||||||
|
@ -264,6 +270,26 @@ def require_user_permission(permission_class, scope=None):
|
||||||
|
|
||||||
require_user_read = require_user_permission(UserReadPermission, scopes.READ_USER)
|
require_user_read = require_user_permission(UserReadPermission, scopes.READ_USER)
|
||||||
require_user_admin = require_user_permission(UserAdminPermission, None)
|
require_user_admin = require_user_permission(UserAdminPermission, None)
|
||||||
|
require_fresh_user_admin = require_user_permission(UserAdminPermission, None)
|
||||||
|
|
||||||
|
def require_fresh_login(func):
|
||||||
|
@add_method_metadata('requires_fresh_login', True)
|
||||||
|
@wraps(func)
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
user = get_authenticated_user()
|
||||||
|
if not user:
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
logger.debug('Checking fresh login for user %s', user.username)
|
||||||
|
|
||||||
|
last_login = session.get('login_time', datetime.datetime.now() - datetime.timedelta(minutes=60))
|
||||||
|
valid_span = datetime.datetime.now() - datetime.timedelta(minutes=10)
|
||||||
|
|
||||||
|
if last_login >= valid_span:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
raise FreshLoginRequired()
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
def require_scope(scope_object):
|
def require_scope(scope_object):
|
||||||
|
|
|
@ -119,6 +119,11 @@ def swagger_route_data(include_internal=False, compact=False):
|
||||||
if internal is not None:
|
if internal is not None:
|
||||||
new_operation['internal'] = True
|
new_operation['internal'] = True
|
||||||
|
|
||||||
|
if include_internal:
|
||||||
|
requires_fresh_login = method_metadata(method, 'requires_fresh_login')
|
||||||
|
if requires_fresh_login is not None:
|
||||||
|
new_operation['requires_fresh_login'] = True
|
||||||
|
|
||||||
if not internal or (internal and include_internal):
|
if not internal or (internal and include_internal):
|
||||||
operations.append(new_operation)
|
operations.append(new_operation)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from app import app, billing as stripe, authentication
|
||||||
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error,
|
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error,
|
||||||
log_action, internal_only, NotFound, require_user_admin, parse_args,
|
log_action, internal_only, NotFound, require_user_admin, parse_args,
|
||||||
query_param, InvalidToken, require_scope, format_date, hide_if, show_if,
|
query_param, InvalidToken, require_scope, format_date, hide_if, show_if,
|
||||||
license_error)
|
license_error, require_fresh_login)
|
||||||
from endpoints.api.subscribe import subscribe
|
from endpoints.api.subscribe import subscribe
|
||||||
from endpoints.common import common_login
|
from endpoints.common import common_login
|
||||||
from data import model
|
from data import model
|
||||||
|
@ -117,10 +117,6 @@ class User(ApiResource):
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'description': 'Fields which can be updated in a user.',
|
'description': 'Fields which can be updated in a user.',
|
||||||
'properties': {
|
'properties': {
|
||||||
'current_password': {
|
|
||||||
'type': 'string',
|
|
||||||
'description': 'The user\'s current password',
|
|
||||||
},
|
|
||||||
'password': {
|
'password': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'description': 'The user\'s password',
|
'description': 'The user\'s password',
|
||||||
|
@ -148,6 +144,7 @@ class User(ApiResource):
|
||||||
return user_view(user)
|
return user_view(user)
|
||||||
|
|
||||||
@require_user_admin
|
@require_user_admin
|
||||||
|
@require_fresh_login
|
||||||
@nickname('changeUserDetails')
|
@nickname('changeUserDetails')
|
||||||
@internal_only
|
@internal_only
|
||||||
@validate_json_request('UpdateUser')
|
@validate_json_request('UpdateUser')
|
||||||
|
@ -156,22 +153,8 @@ class User(ApiResource):
|
||||||
user = get_authenticated_user()
|
user = get_authenticated_user()
|
||||||
user_data = request.get_json()
|
user_data = request.get_json()
|
||||||
|
|
||||||
def verify_current_password(user, user_data):
|
|
||||||
current_password = user_data.get('current_password', '')
|
|
||||||
|
|
||||||
verified = False
|
|
||||||
try:
|
|
||||||
verified = model.verify_user(user.username, current_password)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not verified:
|
|
||||||
raise request_error(message='Current password does not match')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if 'password' in user_data:
|
if 'password' in user_data:
|
||||||
verify_current_password(user, user_data)
|
|
||||||
|
|
||||||
logger.debug('Changing password for user: %s', user.username)
|
logger.debug('Changing password for user: %s', user.username)
|
||||||
log_action('account_change_password', user.username)
|
log_action('account_change_password', user.username)
|
||||||
model.change_password(user, user_data['password'])
|
model.change_password(user, user_data['password'])
|
||||||
|
@ -181,8 +164,6 @@ class User(ApiResource):
|
||||||
model.change_invoice_email(user, user_data['invoice_email'])
|
model.change_invoice_email(user, user_data['invoice_email'])
|
||||||
|
|
||||||
if 'email' in user_data and user_data['email'] != user.email:
|
if 'email' in user_data and user_data['email'] != user.email:
|
||||||
verify_current_password(user, user_data)
|
|
||||||
|
|
||||||
new_email = user_data['email']
|
new_email = user_data['email']
|
||||||
if model.find_user_by_email(new_email):
|
if model.find_user_by_email(new_email):
|
||||||
# Email already used.
|
# Email already used.
|
||||||
|
@ -377,6 +358,37 @@ class Signin(ApiResource):
|
||||||
return conduct_signin(username, password)
|
return conduct_signin(username, password)
|
||||||
|
|
||||||
|
|
||||||
|
@resource('/v1/signin/verify')
|
||||||
|
@internal_only
|
||||||
|
class VerifyUser(ApiResource):
|
||||||
|
""" Operations for verifying the existing user. """
|
||||||
|
schemas = {
|
||||||
|
'VerifyUser': {
|
||||||
|
'id': 'VerifyUser',
|
||||||
|
'type': 'object',
|
||||||
|
'description': 'Information required to verify the signed in user.',
|
||||||
|
'required': [
|
||||||
|
'password',
|
||||||
|
],
|
||||||
|
'properties': {
|
||||||
|
'password': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': 'The user\'s password',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
@require_user_admin
|
||||||
|
@nickname('verifyUser')
|
||||||
|
@validate_json_request('VerifyUser')
|
||||||
|
def post(self):
|
||||||
|
""" Verifies the signed in the user with the specified credentials. """
|
||||||
|
signin_data = request.get_json()
|
||||||
|
password = signin_data['password']
|
||||||
|
return conduct_signin(get_authenticated_user().username, password)
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/signout')
|
@resource('/v1/signout')
|
||||||
@internal_only
|
@internal_only
|
||||||
class Signout(ApiResource):
|
class Signout(ApiResource):
|
||||||
|
|
|
@ -2,8 +2,9 @@ import logging
|
||||||
import urlparse
|
import urlparse
|
||||||
import json
|
import json
|
||||||
import string
|
import string
|
||||||
|
import datetime
|
||||||
|
|
||||||
from flask import make_response, render_template, request, abort
|
from flask import make_response, render_template, request, abort, session
|
||||||
from flask.ext.login import login_user, UserMixin
|
from flask.ext.login import login_user, UserMixin
|
||||||
from flask.ext.principal import identity_changed
|
from flask.ext.principal import identity_changed
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
|
@ -112,6 +113,7 @@ def common_login(db_user):
|
||||||
logger.debug('Successfully signed in as: %s' % db_user.username)
|
logger.debug('Successfully signed in as: %s' % db_user.username)
|
||||||
new_identity = QuayDeferredPermissionUser(db_user.username, 'username', {scopes.DIRECT_LOGIN})
|
new_identity = QuayDeferredPermissionUser(db_user.username, 'username', {scopes.DIRECT_LOGIN})
|
||||||
identity_changed.send(app, identity=new_identity)
|
identity_changed.send(app, identity=new_identity)
|
||||||
|
session['login_time'] = datetime.datetime.now()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logger.debug('User could not be logged in, inactive?.')
|
logger.debug('User could not be logged in, inactive?.')
|
||||||
|
|
|
@ -713,7 +713,7 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
||||||
return config;
|
return config;
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
$provide.factory('ApiService', ['Restangular', function(Restangular) {
|
$provide.factory('ApiService', ['Restangular', '$q', function(Restangular, $q) {
|
||||||
var apiService = {};
|
var apiService = {};
|
||||||
|
|
||||||
var getResource = function(path, opt_background) {
|
var getResource = function(path, opt_background) {
|
||||||
|
@ -810,6 +810,65 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var freshLoginFailCheck = function(opName, opArgs) {
|
||||||
|
return function(resp) {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
|
||||||
|
// If the error is a fresh login required, show the dialog.
|
||||||
|
if (resp.status == 401 && resp.data['error_type'] == 'fresh_login_required') {
|
||||||
|
bootbox.dialog({
|
||||||
|
"message": 'It has been more than a few minutes since you last logged in, ' +
|
||||||
|
'so please verify your password to perform this sensitive operation:' +
|
||||||
|
'<form style="margin-top: 10px" action="javascript:void(0)">' +
|
||||||
|
'<input id="freshPassword" class="form-control" type="password" placeholder="Current Password">' +
|
||||||
|
'</form>',
|
||||||
|
"title": 'Please Verify',
|
||||||
|
"buttons": {
|
||||||
|
"verify": {
|
||||||
|
"label": "Verify",
|
||||||
|
"className": "btn-success",
|
||||||
|
"callback": function() {
|
||||||
|
var info = {
|
||||||
|
'password': $('#freshPassword').val()
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#freshPassword').val('');
|
||||||
|
|
||||||
|
// Conduct the sign in of the user.
|
||||||
|
apiService.verifyUser(info).then(function() {
|
||||||
|
// On success, retry the operation. if it succeeds, then resolve the
|
||||||
|
// deferred promise with the result. Otherwise, reject the same.
|
||||||
|
apiService[opName].apply(apiService, opArgs).then(function(resp) {
|
||||||
|
deferred.resolve(resp);
|
||||||
|
}, function(resp) {
|
||||||
|
deferred.reject(resp);
|
||||||
|
});
|
||||||
|
}, function(resp) {
|
||||||
|
// Reject with the sign in error.
|
||||||
|
deferred.reject({'data': {'message': 'Invalid verification credentials'}});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"close": {
|
||||||
|
"label": "Cancel",
|
||||||
|
"className": "btn-default",
|
||||||
|
"callback": function() {
|
||||||
|
deferred.reject(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return a new promise. We'll accept or reject it based on the result
|
||||||
|
// of the login.
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we just 'raise' the error via the reject method on the promise.
|
||||||
|
return $q.reject(resp);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
var buildMethodsForOperation = function(operation, resource, resourceMap) {
|
var buildMethodsForOperation = function(operation, resource, resourceMap) {
|
||||||
var method = operation['method'].toLowerCase();
|
var method = operation['method'].toLowerCase();
|
||||||
var operationName = operation['nickname'];
|
var operationName = operation['nickname'];
|
||||||
|
@ -823,7 +882,15 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
||||||
'ignoreLoadingBar': true
|
'ignoreLoadingBar': true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return one['custom' + method.toUpperCase()](opt_options);
|
|
||||||
|
var opObj = one['custom' + method.toUpperCase()](opt_options);
|
||||||
|
|
||||||
|
// If the operation requires_fresh_login, then add a specialized error handler that
|
||||||
|
// will defer the operation's result if sudo is requested.
|
||||||
|
if (operation['requires_fresh_login']) {
|
||||||
|
opObj = opObj.catch(freshLoginFailCheck(operationName, arguments));
|
||||||
|
}
|
||||||
|
return opObj;
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the method for the operation is a GET, add an operationAsResource method.
|
// If the method for the operation is a GET, add an operationAsResource method.
|
||||||
|
@ -3923,9 +3990,11 @@ quayApp.directive('billingOptions', function () {
|
||||||
|
|
||||||
var save = function() {
|
var save = function() {
|
||||||
$scope.working = true;
|
$scope.working = true;
|
||||||
|
|
||||||
|
var errorHandler = ApiService.errorDisplay('Could not change user details');
|
||||||
ApiService.changeDetails($scope.organization, $scope.obj).then(function(resp) {
|
ApiService.changeDetails($scope.organization, $scope.obj).then(function(resp) {
|
||||||
$scope.working = false;
|
$scope.working = false;
|
||||||
});
|
}, errorHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
var checkSave = function() {
|
var checkSave = function() {
|
||||||
|
@ -5699,12 +5768,11 @@ quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanServi
|
||||||
|
|
||||||
// Handle session expiration.
|
// Handle session expiration.
|
||||||
Restangular.setErrorInterceptor(function(response) {
|
Restangular.setErrorInterceptor(function(response) {
|
||||||
if (response.status == 401) {
|
if (response.status == 401 && response.data['error_type'] == 'invalid_token' &&
|
||||||
if (response.data['session_required'] == null || response.data['session_required'] === true) {
|
response.data['session_required'] !== false) {
|
||||||
$('#sessionexpiredModal').modal({});
|
$('#sessionexpiredModal').modal({});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status == 503) {
|
if (response.status == 503) {
|
||||||
$('#cannotContactService').modal({});
|
$('#cannotContactService').modal({});
|
||||||
|
|
|
@ -1763,7 +1763,6 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
|
||||||
|
|
||||||
// Reset the form.
|
// Reset the form.
|
||||||
delete $scope.cuser['repeatEmail'];
|
delete $scope.cuser['repeatEmail'];
|
||||||
delete $scope.cuser['current_password'];
|
|
||||||
|
|
||||||
$scope.changeEmailForm.$setPristine();
|
$scope.changeEmailForm.$setPristine();
|
||||||
}, function(result) {
|
}, function(result) {
|
||||||
|
@ -1778,14 +1777,14 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
|
||||||
$scope.updatingUser = true;
|
$scope.updatingUser = true;
|
||||||
$scope.changePasswordSuccess = false;
|
$scope.changePasswordSuccess = false;
|
||||||
|
|
||||||
ApiService.changeUserDetails($scope.cuser).then(function() {
|
ApiService.changeUserDetails($scope.cuser).then(function(resp) {
|
||||||
|
|
||||||
$scope.updatingUser = false;
|
$scope.updatingUser = false;
|
||||||
$scope.changePasswordSuccess = true;
|
$scope.changePasswordSuccess = true;
|
||||||
|
|
||||||
// Reset the form
|
// Reset the form
|
||||||
delete $scope.cuser['password']
|
delete $scope.cuser['password']
|
||||||
delete $scope.cuser['repeatPassword']
|
delete $scope.cuser['repeatPassword']
|
||||||
delete $scope.cuser['current_password'];
|
|
||||||
|
|
||||||
$scope.changePasswordForm.$setPristine();
|
$scope.changePasswordForm.$setPristine();
|
||||||
|
|
||||||
|
|
|
@ -128,8 +128,6 @@
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<form class="form-change col-md-6" id="changeEmailForm" name="changeEmailForm" ng-submit="changeEmail()"
|
<form class="form-change col-md-6" id="changeEmailForm" name="changeEmailForm" ng-submit="changeEmail()"
|
||||||
ng-show="!awaitingConfirmation && !registering">
|
ng-show="!awaitingConfirmation && !registering">
|
||||||
<input type="password" class="form-control" placeholder="Your current password" ng-model="cuser.current_password" required
|
|
||||||
ng-pattern="/^.{8,}$/">
|
|
||||||
<input type="email" class="form-control" placeholder="Your new e-mail address" ng-model="cuser.email" required>
|
<input type="email" class="form-control" placeholder="Your new e-mail address" ng-model="cuser.email" required>
|
||||||
<button class="btn btn-primary" ng-disabled="changeEmailForm.$invalid || cuser.email == user.email" type="submit">Change E-mail Address</button>
|
<button class="btn btn-primary" ng-disabled="changeEmailForm.$invalid || cuser.email == user.email" type="submit">Change E-mail Address</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -153,8 +151,6 @@
|
||||||
<div ng-show="!updatingUser" class="panel-body">
|
<div ng-show="!updatingUser" class="panel-body">
|
||||||
<form class="form-change col-md-6" id="changePasswordForm" name="changePasswordForm" ng-submit="changePassword()"
|
<form class="form-change col-md-6" id="changePasswordForm" name="changePasswordForm" ng-submit="changePassword()"
|
||||||
ng-show="!awaitingConfirmation && !registering">
|
ng-show="!awaitingConfirmation && !registering">
|
||||||
<input type="password" class="form-control" placeholder="Your current password" ng-model="cuser.current_password" required
|
|
||||||
ng-pattern="/^.{8,}$/">
|
|
||||||
<input type="password" class="form-control" placeholder="Your new password" ng-model="cuser.password" required
|
<input type="password" class="form-control" placeholder="Your new password" ng-model="cuser.password" required
|
||||||
ng-pattern="/^.{8,}$/">
|
ng-pattern="/^.{8,}$/">
|
||||||
<input type="password" class="form-control" placeholder="Verify your new password" ng-model="cuser.repeatPassword"
|
<input type="password" class="form-control" placeholder="Verify your new password" ng-model="cuser.repeatPassword"
|
||||||
|
|
|
@ -23,7 +23,8 @@ from endpoints.api.trigger import (BuildTriggerActivate, BuildTriggerSources, Bu
|
||||||
from endpoints.api.repoemail import RepositoryAuthorizedEmail
|
from endpoints.api.repoemail import RepositoryAuthorizedEmail
|
||||||
from endpoints.api.repositorynotification import RepositoryNotification, RepositoryNotificationList
|
from endpoints.api.repositorynotification import RepositoryNotification, RepositoryNotificationList
|
||||||
from endpoints.api.user import (PrivateRepositories, ConvertToOrganization, Recovery, Signout,
|
from endpoints.api.user import (PrivateRepositories, ConvertToOrganization, Recovery, Signout,
|
||||||
Signin, User, UserAuthorizationList, UserAuthorization, UserNotification)
|
Signin, User, UserAuthorizationList, UserAuthorization, UserNotification,
|
||||||
|
VerifyUser)
|
||||||
from endpoints.api.repotoken import RepositoryToken, RepositoryTokenList
|
from endpoints.api.repotoken import RepositoryToken, RepositoryTokenList
|
||||||
from endpoints.api.prototype import PermissionPrototype, PermissionPrototypeList
|
from endpoints.api.prototype import PermissionPrototype, PermissionPrototypeList
|
||||||
from endpoints.api.logs import UserLogs, OrgLogs, RepositoryLogs
|
from endpoints.api.logs import UserLogs, OrgLogs, RepositoryLogs
|
||||||
|
@ -434,6 +435,24 @@ class TestSignin(ApiTestCase):
|
||||||
self._run_test('POST', 403, 'devtable', {u'username': 'E9RY', u'password': 'LQ0N'})
|
self._run_test('POST', 403, 'devtable', {u'username': 'E9RY', u'password': 'LQ0N'})
|
||||||
|
|
||||||
|
|
||||||
|
class TestVerifyUser(ApiTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
ApiTestCase.setUp(self)
|
||||||
|
self._set_url(VerifyUser)
|
||||||
|
|
||||||
|
def test_post_anonymous(self):
|
||||||
|
self._run_test('POST', 401, None, {u'password': 'LQ0N'})
|
||||||
|
|
||||||
|
def test_post_freshuser(self):
|
||||||
|
self._run_test('POST', 403, 'freshuser', {u'password': 'LQ0N'})
|
||||||
|
|
||||||
|
def test_post_reader(self):
|
||||||
|
self._run_test('POST', 403, 'reader', {u'password': 'LQ0N'})
|
||||||
|
|
||||||
|
def test_post_devtable(self):
|
||||||
|
self._run_test('POST', 200, 'devtable', {u'password': 'password'})
|
||||||
|
|
||||||
|
|
||||||
class TestListPlans(ApiTestCase):
|
class TestListPlans(ApiTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
ApiTestCase.setUp(self)
|
ApiTestCase.setUp(self)
|
||||||
|
@ -473,13 +492,13 @@ class TestUser(ApiTestCase):
|
||||||
self._run_test('PUT', 401, None, {})
|
self._run_test('PUT', 401, None, {})
|
||||||
|
|
||||||
def test_put_freshuser(self):
|
def test_put_freshuser(self):
|
||||||
self._run_test('PUT', 200, 'freshuser', {})
|
self._run_test('PUT', 401, 'freshuser', {})
|
||||||
|
|
||||||
def test_put_reader(self):
|
def test_put_reader(self):
|
||||||
self._run_test('PUT', 200, 'reader', {})
|
self._run_test('PUT', 401, 'reader', {})
|
||||||
|
|
||||||
def test_put_devtable(self):
|
def test_put_devtable(self):
|
||||||
self._run_test('PUT', 200, 'devtable', {})
|
self._run_test('PUT', 401, 'devtable', {})
|
||||||
|
|
||||||
def test_post_anonymous(self):
|
def test_post_anonymous(self):
|
||||||
self._run_test('POST', 400, None, {u'username': 'T946', u'password': '0SG4', u'email': 'MENT'})
|
self._run_test('POST', 400, None, {u'username': 'T946', u'password': '0SG4', u'email': 'MENT'})
|
||||||
|
|
|
@ -172,14 +172,14 @@ class TestCSRFFailure(ApiTestCase):
|
||||||
|
|
||||||
# Make sure a simple post call succeeds.
|
# Make sure a simple post call succeeds.
|
||||||
self.putJsonResponse(User,
|
self.putJsonResponse(User,
|
||||||
data=dict(password='newpasswordiscool', current_password='password'))
|
data=dict(password='newpasswordiscool'))
|
||||||
|
|
||||||
# Change the session's CSRF token.
|
# Change the session's CSRF token.
|
||||||
self.setCsrfToken('someinvalidtoken')
|
self.setCsrfToken('someinvalidtoken')
|
||||||
|
|
||||||
# Verify that the call now fails.
|
# Verify that the call now fails.
|
||||||
self.putJsonResponse(User,
|
self.putJsonResponse(User,
|
||||||
data=dict(password='newpasswordiscool', current_password='password'),
|
data=dict(password='newpasswordiscool'),
|
||||||
expected_code=403)
|
expected_code=403)
|
||||||
|
|
||||||
|
|
||||||
|
@ -325,28 +325,14 @@ class TestChangeUserDetails(ApiTestCase):
|
||||||
def test_changepassword(self):
|
def test_changepassword(self):
|
||||||
self.login(READ_ACCESS_USER)
|
self.login(READ_ACCESS_USER)
|
||||||
self.putJsonResponse(User,
|
self.putJsonResponse(User,
|
||||||
data=dict(password='newpasswordiscool', current_password='password'))
|
data=dict(password='newpasswordiscool'))
|
||||||
self.login(READ_ACCESS_USER, password='newpasswordiscool')
|
self.login(READ_ACCESS_USER, password='newpasswordiscool')
|
||||||
|
|
||||||
def test_changepassword_invalidpasswor(self):
|
|
||||||
self.login(READ_ACCESS_USER)
|
|
||||||
self.putJsonResponse(User,
|
|
||||||
data=dict(password='newpasswordiscool', current_password='notcorrect'),
|
|
||||||
expected_code=400)
|
|
||||||
|
|
||||||
def test_changeeemail(self):
|
def test_changeeemail(self):
|
||||||
self.login(READ_ACCESS_USER)
|
self.login(READ_ACCESS_USER)
|
||||||
|
|
||||||
self.putJsonResponse(User,
|
self.putJsonResponse(User,
|
||||||
data=dict(email='test+foo@devtable.com', current_password='password'))
|
data=dict(email='test+foo@devtable.com'))
|
||||||
|
|
||||||
def test_changeeemail_invalidpassword(self):
|
|
||||||
self.login(READ_ACCESS_USER)
|
|
||||||
|
|
||||||
self.putJsonResponse(User,
|
|
||||||
data=dict(email='test+foo@devtable.com', current_password='notcorrect'),
|
|
||||||
expected_code=400)
|
|
||||||
|
|
||||||
|
|
||||||
def test_changeinvoiceemail(self):
|
def test_changeinvoiceemail(self):
|
||||||
self.login(READ_ACCESS_USER)
|
self.login(READ_ACCESS_USER)
|
||||||
|
|
Reference in a new issue