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

1288/more users per token #1368

Merged
merged 12 commits into from Feb 15, 2019
154 changes: 154 additions & 0 deletions migrations/versions/48ee74b8a7c8_.py
@@ -0,0 +1,154 @@
"""Add tokenowner table and move tokenuser data to new table

Revision ID: 48ee74b8a7c8
Revises: cb6d7b7bae63
Create Date: 2019-01-09 16:58:03.968193

"""

# revision identifiers, used by Alembic.
revision = '48ee74b8a7c8'
down_revision = 'cb6d7b7bae63'


from alembic import op
import sqlalchemy as sa
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.schema import Sequence
from sqlalchemy import orm
from privacyidea.models import ResolverRealm, TokenRealm
import sys


db = SQLAlchemy()


class Realm(db.Model):
__tablename__ = 'realm'
__table_args__ = {'mysql_row_format': 'DYNAMIC'}
id = db.Column(db.Integer, Sequence("realm_seq"), primary_key=True,
nullable=False)
name = db.Column(db.Unicode(255), default=u'',
unique=True, nullable=False)
default = db.Column(db.Boolean(), default=False)
option = db.Column(db.Unicode(40), default=u'')


class TokenOwner(db.Model):
__tablename__ = 'tokenowner'
id = db.Column(db.Integer(), Sequence("tokenowner_seq"), primary_key=True)
token_id = db.Column(db.Integer(), db.ForeignKey('token.id'))
token = db.relationship('Token', lazy='joined', backref='token_list')
resolver = db.Column(db.Unicode(120), default=u'', index=True)
user_id = db.Column(db.Unicode(320), default=u'', index=True)
realm_id = db.Column(db.Integer(), db.ForeignKey('realm.id'))
realm = db.relationship('Realm', lazy='joined', backref='realm_list')


class Token(db.Model):
__tablename__ = 'token'
__table_args__ = {'mysql_row_format': 'DYNAMIC'}
id = db.Column(db.Integer, Sequence("token_seq"),
primary_key=True,
nullable=False)
serial = db.Column(db.Unicode(40), default=u'',
unique=True,
nullable=False,
index=True)
resolver = db.Column(db.Unicode(120), default=u'',
index=True)
resolver_type = db.Column(db.Unicode(120), default=u'')
user_id = db.Column(db.Unicode(320),
default=u'', index=True)


def upgrade():
try:
op.create_table('tokenowner',
sa.Column('id', sa.Integer()),
sa.Column('token_id', sa.Integer(), nullable=True),
sa.Column('resolver', sa.Unicode(length=120), nullable=True),
sa.Column('user_id', sa.Unicode(length=320), nullable=True),
sa.Column('realm_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['realm_id'], ['realm.id'], ),
sa.ForeignKeyConstraint(['token_id'], ['token.id'], ),
sa.PrimaryKeyConstraint('id')
)
fredreichbier marked this conversation as resolved.
Show resolved Hide resolved
op.create_index(op.f('ix_tokenowner_resolver'), 'tokenowner', ['resolver'], unique=False)
op.create_index(op.f('ix_tokenowner_user_id'), 'tokenowner', ['user_id'], unique=False)
except Exception as exx:
print("Can not create table 'tokenowner'. It probably already exists")
print (exx)

try:
bind = op.get_bind()
session = orm.Session(bind=bind)
# For each token, that has an owner, create a tokenowner entry
for token in session.query(Token).filter(Token.user_id):
token_realms = TokenRealm.query.filter(TokenRealm.token_id == token.id).all()
realm_id = None
if not token_realms:
sys.stderr.write(u"{serial!s}, {userid!s}, {resolver!s}, "
u"Error while migrating token assignment. "
u"This token has no realm assignments!".format(serial=token.serial,
userid=token.user_id,
resolver=token.resolver))
elif len(token_realms) == 1:
realm_id = token_realms[0].realm_id
elif len(token_realms) > 1:
# If the resolver is only contained in one realm, we fetch the realms:
reso_realms = ResolverRealm.query.filter(ResolverRealm.resolver == token.resolver).all()
if not reso_realms:
sys.stderr.write(u"{serial!s}, {userid!s}, {resolver!s}, "
u"The token is assigned, but the assigned resolver is not "
u"contained in any realm!".format(serial=token.serial,
userid=token.user_id,
resolver=token.resolver))
elif len(reso_realms) == 1:
# The resolver is only in one realm, so this is the new realm of the token!
realm_id = reso_realms[0].realm_id
elif len(reso_realms) > 1:
# The resolver is contained in two realms, we have to apply more logic between the realms in which
# the resolver is contained and the realms, to which the token is assigend.
found_realm_ids = []
for token_realm in token_realms:
if token_realm.realm_id in [r.realm_id for r in reso_realms]:
# The token realm, that also fits the resolver_realm is used as owner realm
found_realm_ids.append(realm_id)
if len(found_realm_ids) > 1:
sys.stderr.write(u"{serial!s}, {userid!s}, {resolver!s}, "
u"Your realm configuration for the token is not distinct!. "
u"The tokenowner could be in multiple realms! "
u"The token is assigned to the following realms and the resolver is also "
u"contained in these realm IDs: {realms!s}.".format(serial=token.serial,
userid=token.user_id,
resolver=token.resolver,
realms=found_realm_ids))
elif len(found_realm_ids) == 1:
realm_id = found_realm_ids[0]
else:
sys.stderr.write(u"{serial!s}, {userid!s}, {resolver!s}, "
u"Can not assign token. The resolver is not contained in any "
u"realms, to which the token is assigned!")

to = TokenOwner(token_id=token.id, user_id=token.user_id,
resolver=token.resolver, realm_id=realm_id)
session.add(to)
session.commit()

# Now we drop the columns
op.drop_column('token', 'user_id')
op.drop_column('token', 'resolver')
op.drop_column('token', 'resolver_type')

except Exception as exx:
print("Failed to migrate token assignment data!")
print (exx)
cornelinux marked this conversation as resolved.
Show resolved Hide resolved


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_tokenowner_user_id'), table_name='tokenowner')
op.drop_index(op.f('ix_tokenowner_resolver'), table_name='tokenowner')
op.drop_table('tokenowner')
# ### end Alembic commands ###
3 changes: 1 addition & 2 deletions privacyidea/lib/eventhandler/base.py
Expand Up @@ -410,8 +410,7 @@ def check_condition(self, options):
return False

if CONDITION.TOKEN_IS_ORPHANED in conditions:
uid = token_obj.get_user_id()
orphaned = uid and not user
orphaned = token_obj.is_orphaned()
check = conditions.get(CONDITION.TOKEN_IS_ORPHANED)
if orphaned and check in ["True", True]:
res = True
Expand Down
7 changes: 3 additions & 4 deletions privacyidea/lib/subscriptions.py
Expand Up @@ -83,10 +83,9 @@ def get_users_with_active_tokens():
:return: Number of users
:rtype: int
"""
from privacyidea.models import Token
sql_query = Token.query.with_entities(Token.resolver,
Token.user_id).filter(Token.active == True,
Token.user_id != "").distinct()
from privacyidea.models import Token, TokenOwner
sql_query = TokenOwner.query.with_entities(TokenOwner.resolver, TokenOwner.user_id)
sql_query = sql_query.filter(Token.active == True).filter(Token.id == TokenOwner.token_id).distinct()
return sql_query.count()


Expand Down
89 changes: 37 additions & 52 deletions privacyidea/lib/token.py
Expand Up @@ -79,11 +79,11 @@
from privacyidea.lib.crypto import generate_password
from privacyidea.lib.log import log_with
from privacyidea.models import (Token, Realm, TokenRealm, Challenge,
MachineToken, TokenInfo)
MachineToken, TokenInfo, TokenOwner)
from privacyidea.lib.config import (get_token_class, get_token_prefix,
get_token_types, get_from_config,
get_inc_fail_count_on_false_pin)
from privacyidea.lib.user import get_user_info, User
from privacyidea.lib.user import User
from privacyidea.lib import _
from privacyidea.lib.realm import realm_is_defined
from privacyidea.lib.resolver import get_resolver_object
Expand Down Expand Up @@ -176,9 +176,9 @@ def _create_token_query(tokentype=None, realm=None, assigned=None, user=None,
if assigned is not None:
# filter if assigned or not
if assigned is False:
sql_query = sql_query.filter(Token.user_id == "")
sql_query = sql_query.filter(Token.owners == None)
fredreichbier marked this conversation as resolved.
Show resolved Hide resolved
elif assigned is True:
sql_query = sql_query.filter(Token.user_id != "")
sql_query = sql_query.filter(Token.owners)
else:
log.warning("assigned value not in [True, False] {0!r}".format(assigned))

Expand All @@ -190,23 +190,29 @@ def _create_token_query(tokentype=None, realm=None, assigned=None, user=None,
TokenRealm.token_id ==
Token.id)).distinct()

if resolver is not None and resolver.strip("*"):
stripped_resolver = None if resolver is None else resolver.strip("*")
stripped_userid = None if userid is None else userid.strip("*")
if stripped_userid or stripped_resolver:
# Join the search with the token owner
sql_query = sql_query.filter(TokenOwner.token_id == Token.id)
fredreichbier marked this conversation as resolved.
Show resolved Hide resolved

if stripped_resolver:
# filter for given resolver
if "*" in resolver:
# match with "like"
sql_query = sql_query.filter(Token.resolver.like(resolver.replace(
sql_query = sql_query.filter(TokenOwner.resolver.like(resolver.replace(
"*", "%")))
else:
sql_query = sql_query.filter(Token.resolver == resolver)
sql_query = sql_query.filter(TokenOwner.resolver == resolver)

if userid is not None and userid.strip("*"):
if stripped_userid:
# filter for given userid
if "*" in userid:
# match with "like"
sql_query = sql_query.filter(Token.user_id.like(userid.replace(
sql_query = sql_query.filter(TokenOwner.user_id.like(userid.replace(
"*", "%")))
else:
sql_query = sql_query.filter(Token.user_id == userid)
sql_query = sql_query.filter(TokenOwner.user_id == userid)

if serial_wildcard is not None and serial_wildcard.strip("*"):
# filter for serial
Expand All @@ -221,12 +227,14 @@ def _create_token_query(tokentype=None, realm=None, assigned=None, user=None,
if user is not None and not user.is_empty():
# filter for the rest of the user.
if user.resolver:
sql_query = sql_query.filter(Token.resolver == user.resolver)
sql_query = sql_query.filter(TokenOwner.token_id == Token.id)
sql_query = sql_query.filter(TokenOwner.resolver == user.resolver)
(uid, _rtype, _resolver) = user.get_user_identifiers()
if uid:
if type(uid) == int:
uid = str(uid)
sql_query = sql_query.filter(Token.user_id == uid)
sql_query = sql_query.filter(TokenOwner.token_id == Token.id)
sql_query = sql_query.filter(TokenOwner.user_id == uid)

if active is not None:
# Filter active or inactive tokens
Expand Down Expand Up @@ -716,34 +724,6 @@ def get_tokenclass_info(tokentype, section=None):
return res


@log_with(log)
def get_all_token_users():
"""
return a dictionary with all tokens, that are assigned to users.
This returns a dictionary with the key being the serial number of
the token and the user information as dict.

:return: dictionary of serial numbers
:rtype: dict
"""
tokens = {}
tokenobject_list = get_tokens(assigned=True)

for tokenobject in tokenobject_list:
user_info = {}
if tokenobject.token.user_id and tokenobject.token.resolver:
user_info = get_user_info(tokenobject.token.user_id,
tokenobject.token.resolver)

if tokenobject.token.user_id and len(user_info) == 0:
user_info['username'] = u'/:no user info:/'

if user_info:
tokens[tokenobject.token.serial] = user_info

return tokens


@log_with(log)
def get_otp(serial, current_time=None):
"""
Expand Down Expand Up @@ -1025,7 +1005,7 @@ def init_token(param, user=None, tokenrealms=None,
# and to the user realm
if user and user.realm:
realms.append(user.realm)
if realms:
if realms or user:
# We need to save the token to the DB, otherwise the Token
# has no id!
db_token.save()
Expand All @@ -1041,7 +1021,7 @@ def init_token(param, user=None, tokenrealms=None,

# Set the user of the token
if user is not None and user.login != "":
tokenobject.set_user(user)
tokenobject.add_user(user)

upd_params = param
tokenobject.update(upd_params)
Expand Down Expand Up @@ -1106,6 +1086,8 @@ def remove_token(serial=None, user=None):
tokenobject.token.id).delete()
TokenRealm.query.filter(TokenRealm.token_id ==
tokenobject.token.id).delete()
TokenOwner.query.filter(TokenOwner.token_id ==
tokenobject.token.id).delete()

tokenobject.token.delete()

Expand Down Expand Up @@ -1186,7 +1168,7 @@ def assign_token(serial, user, pin=None, encrypt_pin=False, err_message=None):
err_message = err_message or "Token already assigned to user {0!r}".format(old_user)
raise TokenAdminError(err_message, id=1103)

tokenobject.set_user(user)
tokenobject.add_user(user)
if pin is not None:
tokenobject.set_pin(pin, encrypt=encrypt_pin)

Expand Down Expand Up @@ -1216,13 +1198,12 @@ def unassign_token(serial, user=None):
"""
tokenobject_list = get_tokens_from_serial_or_user(serial=serial, user=user)
for tokenobject in tokenobject_list:
tokenobject.token.user_id = ""
tokenobject.token.resolver = ""
tokenobject.token.resolver_type = ""
tokenobject.set_pin("")
tokenobject.set_failcount(0)

try:
# Delete the tokenowner entry
TokenOwner.query.filter(TokenOwner.token_id == tokenobject.token.id).delete()
tokenobject.save()
except Exception as e: # pragma: no cover
log.error('update token DB failed')
Expand Down Expand Up @@ -1772,15 +1753,19 @@ def copy_token_user(serial_from, serial_to):
"""
tokenobject_from = get_one_token(serial=serial_from)
tokenobject_to = get_one_token(serial=serial_to)
user_id = tokenobject_from.token.user_id
resolver = tokenobject_from.token.resolver
resolver_type = tokenobject_from.token.resolver_type
tokenobject_to.set_user_identifiers(user_id, resolver,
resolver_type)

# For backward compatibility we remove the potentially old users from the token.
# TODO: Later we probably want to be able to "add" new users to a token.
unassign_token(serial_to)
TokenOwner(token_id=tokenobject_to.token.id,
user_id=tokenobject_from.token.first_owner.user_id,
realm_id=tokenobject_from.token.first_owner.realm_id,
resolver=tokenobject_from.token.first_owner.resolver).save()
fredreichbier marked this conversation as resolved.
Show resolved Hide resolved
# Also copy other assigned realms of the token.
copy_token_realms(serial_from, serial_to)
tokenobject_to.save()
return True


@check_copy_serials
def copy_token_realms(serial_from, serial_to):
"""
Expand Down