Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle non-existent resources more strictly #1310

Merged
merged 7 commits into from Nov 19, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 26 additions & 1 deletion privacyidea/api/before_after.py
Expand Up @@ -64,7 +64,7 @@
from privacyidea.api.lib.postpolicy import postrequest, sign_response
from ..lib.error import (privacyIDEAError,
AuthError, UserError,
PolicyError)
PolicyError, ResourceNotFoundError)
from privacyidea.lib.utils import get_client_ip
from privacyidea.lib.user import User

Expand Down Expand Up @@ -295,6 +295,31 @@ def policy_error(error):
return send_error(error.message, error_code=error.id), 403


@system_blueprint.app_errorhandler(ResourceNotFoundError)
@realm_blueprint.app_errorhandler(ResourceNotFoundError)
@defaultrealm_blueprint.app_errorhandler(ResourceNotFoundError)
@resolver_blueprint.app_errorhandler(ResourceNotFoundError)
@policy_blueprint.app_errorhandler(ResourceNotFoundError)
@user_blueprint.app_errorhandler(ResourceNotFoundError)
@token_blueprint.app_errorhandler(ResourceNotFoundError)
@audit_blueprint.app_errorhandler(ResourceNotFoundError)
@application_blueprint.app_errorhandler(ResourceNotFoundError)
@smtpserver_blueprint.app_errorhandler(ResourceNotFoundError)
@register_blueprint.app_errorhandler(ResourceNotFoundError)
@recover_blueprint.app_errorhandler(ResourceNotFoundError)
@subscriptions_blueprint.app_errorhandler(ResourceNotFoundError)
@postrequest(sign_response, request=request)
def resource_not_found_error(error):
"""
This function is called when an ResourceNotFoundError occurs.
It sends a 404.
"""
if "audit_object" in g:
g.audit_object.log({"info": error.message})
g.audit_object.finalize_log()
return send_error(error.message, error_code=error.id), 404


@system_blueprint.app_errorhandler(privacyIDEAError)
@realm_blueprint.app_errorhandler(privacyIDEAError)
@defaultrealm_blueprint.app_errorhandler(privacyIDEAError)
Expand Down
8 changes: 4 additions & 4 deletions privacyidea/api/lib/postpolicy.py
Expand Up @@ -46,7 +46,7 @@
from flask import g, current_app, make_response
from privacyidea.lib.policy import SCOPE, ACTION, AUTOASSIGNVALUE
from privacyidea.lib.user import get_user_from_param
from privacyidea.lib.token import get_tokens, assign_token, get_realms_of_token
from privacyidea.lib.token import get_tokens, assign_token, get_realms_of_token, get_one_token
from privacyidea.lib.machine import get_hostname, get_auth_items
from .prepolicy import check_max_token_user, check_max_token_realm
import functools
Expand Down Expand Up @@ -430,7 +430,7 @@ def save_pin_change(request, response, serial=None):
scope=SCOPE.ENROLL, realm=realm,
client=g.client_ip, active=True)
if pinpol:
token = get_tokens(serial=serial)[0]
token = get_one_token(serial=serial)
token.set_next_pin_change(diff="0d")

elif g.logged_in_user.get("role") == ROLE.USER:
Expand All @@ -439,7 +439,7 @@ def save_pin_change(request, response, serial=None):
pin = request.all_data.get("pin")
# The user sets a pin or enrolls a token. -> delete the pin_change
if otppin or pin:
token = get_tokens(serial=serial)[0]
token = get_one_token(serial=serial)
token.del_tokeninfo("next_pin_change")

# If there is a change_pin_every policy, we need to set the PIN
Expand All @@ -448,7 +448,7 @@ def save_pin_change(request, response, serial=None):
ACTION.CHANGE_PIN_EVERY, scope=SCOPE.ENROLL,
realm=realm, client=g.client_ip, unique=True)
if pinpol:
token = get_tokens(serial=serial)[0]
token = get_one_token(serial=serial)
token.set_next_pin_change(diff=pinpol[0])

# we do not modify the response!
Expand Down
23 changes: 11 additions & 12 deletions privacyidea/api/token.py
Expand Up @@ -426,9 +426,8 @@ def assign_api():
err_message = "Token already assigned to another user."
else:
err_message = None
res = assign_token(serial, user, pin=pin, encrypt_pin=encrypt_pin,
err_message=err_message)
g.audit_object.log({"success": True})
res = assign_token(serial, user, pin=pin, encrypt_pin=encrypt_pin, err_message=err_message)
g.audit_object.log({"success": res})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{"success": True}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, assign_token either raises an error or returns True, so if this line is executed at all, res will always be True.

But maybe we should make this more explicit?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is more clear to revert it to "True" again.Thanks a lot.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, did this in dc564c7

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return send_result(res)


Expand All @@ -442,13 +441,15 @@ def unassign_api():
You can either provide "serial" as an argument to unassign this very
token or you can provide user and realm, to unassign all tokens of a user.

:return: In case of success it returns "value": True.
:rtype: json object
:return: In case of success it returns the number of unassigned tokens in "value".
:rtype: JSON object
"""
user = get_user_from_param(request.all_data, optional)
serial = getParam(request.all_data, "serial", optional)
g.audit_object.log({"serial": serial})

res = unassign_token(serial, user=user)
g.audit_object.log({"success": True})
g.audit_object.log({"success": res > 0})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By changing "True" to "res > 0" this would mean, if I assign "all tokens" of a user, who has not token, I would get a success=False.
Is that, what we want?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to True again in dc564c7 :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return send_result(res)


Expand Down Expand Up @@ -546,13 +547,11 @@ def disable_api(serial=None):
@prepolicy(check_base_action, request, action=ACTION.DELETE)
@event("token_delete", request, g)
@log_with(log)
def delete_api(serial=None):
def delete_api(serial):
"""
Delete a token by its serial number or delete all tokens of a user.
Delete a token by its serial number.

:jsonparam serial: The serial number of a single token.
:jsonparam user: The username of the user, whose tokens should be deleted.
:jsonparam realm: The realm of the user.

:return: In case of success it return the number of deleted tokens in
"value"
Expand Down Expand Up @@ -800,9 +799,9 @@ def tokenrealm_api(serial=None):
realm_list = [r.strip() for r in realms.split(",")]
g.audit_object.log({"serial": serial})

res = set_realms(serial, realms=realm_list)
set_realms(serial, realms=realm_list)
g.audit_object.log({"success": True})
return send_result(res == 1)
return send_result(True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does also not seem quite consistent to me.
In this case we always add "success": True to the audit. Setting an empty list would remove the realms?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm not sure here: set_realms only gets a serial, not a user, so it will raise a ResourceNotFound if the token with the serial does not exist. So if the line after set_realms is executed, I would say the request is successful?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can forget my comment. It was in conjunction with the other success-audit-thingies.
I also think that we can return True, if realms were successfully set, no matter if they were overwritten, added or deleted!
Works for me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍



@token_blueprint.route('/load/<filename>', methods=['POST'])
Expand Down
3 changes: 2 additions & 1 deletion privacyidea/lib/auth.py
Expand Up @@ -25,6 +25,7 @@
from privacyidea.lib.token import check_user_pass
from privacyidea.lib.policydecorators import libpolicy, login_mode
from privacyidea.lib.crypto import hash_with_pepper, verify_with_pepper
from privacyidea.lib.utils import fetch_one_resource


class ROLE(object):
Expand Down Expand Up @@ -87,7 +88,7 @@ def get_db_admin(username):

def delete_db_admin(username):
print("Deleting admin {0!s}".format(username))
Admin.query.filter(Admin.username == username).first().delete()
fetch_one_resource(Admin, username=username).delete()


@libpolicy(login_mode)
Expand Down
12 changes: 3 additions & 9 deletions privacyidea/lib/caconnector.py
Expand Up @@ -40,7 +40,7 @@
from .error import ConfigAdminError
from sqlalchemy import func
from .crypto import encryptPassword, decryptPassword
from privacyidea.lib.utils import (sanity_name_check, get_data_from_params)
from privacyidea.lib.utils import (sanity_name_check, get_data_from_params, fetch_one_resource)
#from privacyidea.lib.cache import cache

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -177,20 +177,14 @@ def get_caconnector_list(filter_caconnector_type=None,
def delete_caconnector(connector_name):
"""
delete a CA connector and all related config entries.
If there was no CA connector, that could be deleted, it returns -1
If there was no CA connector, that could be deleted, a ResourceNotFoundError is raised.

:param connector_name: The name of the CA connector that is to be deleted
:type connector_name: basestring
:return: The Id of the resolver
:rtype: int
"""
ret = -1

conn = CAConnector.query.filter_by(name=connector_name).first()
if conn:
conn.delete()
ret = conn.id
return ret
return fetch_one_resource(CAConnector, name=connector_name).delete()


@log_with(log)
Expand Down
8 changes: 7 additions & 1 deletion privacyidea/lib/error.py
Expand Up @@ -48,6 +48,7 @@ class ERROR:
AUTHENTICATE_TOKEN_EXPIRED = 4305
AUTHENTICATE_MISSING_RIGHT = 4306
CA = 503
RESOURCE_NOT_FOUND = 601
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did yo choose 601? Why not a 404?
Or did you want to leave our 4xx errors to the authentication process? (I do not know the number-spaces myself)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

HSM = 707
SELFSERVICE = 807
SERVER = 903
Expand Down Expand Up @@ -112,7 +113,12 @@ class AuthError(privacyIDEAError):
def __init__(self, description, id=ERROR.AUTHENTICATE, details=None):
self.details = details
privacyIDEAError.__init__(self, description=description, id=id)



class ResourceNotFoundError(privacyIDEAError):
def __init__(self, description, id=ERROR.RESOURCE_NOT_FOUND):
privacyIDEAError.__init__(self, description=description, id=id)


class PolicyError(privacyIDEAError):
def __init__(self, description, id=ERROR.POLICY):
Expand Down
9 changes: 3 additions & 6 deletions privacyidea/lib/event.py
Expand Up @@ -22,6 +22,7 @@
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
from privacyidea.lib.utils import fetch_one_resource
from privacyidea.models import EventHandler, EventHandlerOption, db
from privacyidea.lib.error import ParameterError
from privacyidea.lib.audit import getAudit
Expand Down Expand Up @@ -178,11 +179,7 @@ def enable_event(event_id, enable=True):
:param event_id: ID of the event
:return:
"""
ev = EventHandler.query.filter_by(id=event_id).first()
if not ev:
raise ParameterError("The event with id '{0!s}' does not "
"exist".format(event_id))

ev = fetch_one_resource(EventHandler, id=event_id)
# Update the event
ev.active = enable
r = ev.save()
Expand Down Expand Up @@ -241,7 +238,7 @@ def delete_event(event_id):
:return:
"""
event_id = int(event_id)
ev = EventHandler.query.filter_by(id=event_id).first()
ev = fetch_one_resource(EventHandler, id=event_id)
r = ev.delete()
return r

Expand Down
6 changes: 3 additions & 3 deletions privacyidea/lib/machine.py
Expand Up @@ -34,6 +34,7 @@
MachineResolver, get_token_id,
get_machineresolver_id,
get_machinetoken_id)
from privacyidea.lib.utils import fetch_one_resource
from netaddr import IPAddress
from sqlalchemy import and_
import logging
Expand Down Expand Up @@ -327,11 +328,10 @@ def list_token_machines(serial):
:return: returns a list of machines and apps
"""
res = []
db_token = Token.query.filter(Token.serial == serial).first()
db_token = fetch_one_resource(Token, serial=serial)

for machine in db_token.machine_list:
MR = MachineResolver.query.filter(MachineResolver.id ==
machine.machineresolver_id).first()
MR = fetch_one_resource(MachineResolver, id=machine.machineresolver_id)
resolver_name = MR.name

option_list = machine.option_list
Expand Down
12 changes: 3 additions & 9 deletions privacyidea/lib/machineresolver.py
Expand Up @@ -40,7 +40,7 @@
from sqlalchemy import func
from .crypto import encryptPassword, decryptPassword
from privacyidea.lib.config import get_machine_resolver_class_dict
from privacyidea.lib.utils import (sanity_name_check, get_data_from_params)
from privacyidea.lib.utils import (sanity_name_check, get_data_from_params, fetch_one_resource)


log = logging.getLogger(__name__)
Expand Down Expand Up @@ -162,20 +162,14 @@ def get_resolver_list(filter_resolver_type=None,
def delete_resolver(resolvername):
"""
delete a machine resolver and all related MachineResolverConfig entries
If there was no resolver, that could be deleted, it returns -1
If there was no resolver, this raises a ResourceNotFoundError.

:param resolvername: the name of the to be deleted resolver
:type resolvername: string
:return: The Id of the resolver
:rtype: int
"""
ret = -1

reso = MachineResolver.query.filter_by(name=resolvername).first()
if reso:
reso.delete()
ret = reso.id
return ret
return fetch_one_resource(MachineResolver, name=resolvername).delete()


@log_with(log)
Expand Down
12 changes: 5 additions & 7 deletions privacyidea/lib/periodictask.py
Expand Up @@ -26,7 +26,8 @@
from croniter import croniter
from dateutil.tz import tzutc, tzlocal

from privacyidea.lib.error import ServerError, ParameterError
from privacyidea.lib.error import ServerError, ParameterError, ResourceNotFoundError
from privacyidea.lib.utils import fetch_one_resource
from privacyidea.lib.task.eventcounter import EventCounterTask
from privacyidea.lib.task.simplestats import SimpleStatsTask
from privacyidea.models import PeriodicTask
Expand Down Expand Up @@ -183,7 +184,7 @@ def get_periodic_task_by_name(name):
"""
periodic_tasks = get_periodic_tasks(name)
if len(periodic_tasks) != 1:
raise ParameterError("The periodic task with unique name {!r} does not exist".format(name))
raise ResourceNotFoundError(u"The periodic task with unique name {!r} does not exist".format(name))
return periodic_tasks[0]


Expand All @@ -199,15 +200,12 @@ def get_periodic_task_by_id(ptask_id):

def _get_periodic_task_entry(ptask_id):
"""
Get a periodic task entry by ID. Raise ParameterError if the task could not be found.
Get a periodic task entry by ID. Raise ResourceNotFoundError if the task could not be found.
This is only for internal use.
:param id: task ID as integer
:return: PeriodicTask object
"""
periodic_task = PeriodicTask.query.filter_by(id=ptask_id).first()
if periodic_task is None:
raise ParameterError("The periodic task with id {!r} does not exist".format(ptask_id))
return periodic_task
return fetch_one_resource(PeriodicTask, id=ptask_id)


def set_periodic_task_last_run(ptask_id, node, last_run_timestamp):
Expand Down
17 changes: 7 additions & 10 deletions privacyidea/lib/policy.py
Expand Up @@ -164,12 +164,12 @@
from flask import current_app
from privacyidea.lib.config import (get_token_classes, get_token_types,
Singleton)
from privacyidea.lib.error import ParameterError, PolicyError
from privacyidea.lib.error import ParameterError, PolicyError, ResourceNotFoundError
from privacyidea.lib.realm import get_realms
from privacyidea.lib.resolver import get_resolver_list
from privacyidea.lib.smtpserver import get_smtpservers
from privacyidea.lib.radiusserver import get_radiusservers
from privacyidea.lib.utils import check_time_in_range, reload_db
from privacyidea.lib.utils import check_time_in_range, reload_db, fetch_one_resource
from privacyidea.lib.user import User
from privacyidea.lib import _
import datetime
Expand Down Expand Up @@ -941,7 +941,7 @@ def enable_policy(name, enable=True):
:return: ID of the policy
"""
if not Policy.query.filter(Policy.name == name).first():
raise ParameterError("The policy with name '{0!s}' does not exist".format(name))
raise ResourceNotFoundError(u"The policy with name '{0!s}' does not exist".format(name))

# Update the policy
p = set_policy(name=name, active=enable)
Expand All @@ -951,17 +951,14 @@ def enable_policy(name, enable=True):
@log_with(log)
def delete_policy(name):
"""
Function to delete one named policy
Function to delete one named policy.
Raise ResourceNotFoundError if there is no such policy.

:param name: the name of the policy to be deleted
:return: the count of the deleted policies.
:return: the ID of the deletec policy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo :-) "deletec"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx :) fixed!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

:rtype: int
"""
res = False
p = Policy.query.filter_by(name=name).first()
if p:
res = p.delete()
return res
return fetch_one_resource(Policy, name=name).delete()


@log_with(log)
Expand Down