Source code for privacyidea.models.tokencontainer

# SPDX-FileCopyrightText: (C) 2025 NetKnights GmbH <https://netknights.it>
# SPDX-FileCopyrightText: (C) 2025 Paul Lettich <paul.lettich@netknights.it>
# SPDX-FileCopyrightText: (C) 2024 Jelina Unger <jelina.unger@netknights.it>
# SPDX-FileCopyrightText: (C) 2024 Nils Behlen <nils.behlen@netknights.it>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# This code is free software; you can redistribute it and/or
# modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
# as published by the Free Software Foundation; either
# version 3 of the License, or any later version.
#
# This code is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with this program.  If not, see <http://www.gnu.org/licenses/>.

from privacyidea.models import db
from privacyidea.models.utils import MethodsMixin
from privacyidea.models.realm import Realm
from privacyidea.models.config import SAFE_STORE
from privacyidea.lib.framework import get_app_config_value
from privacyidea.lib.utils import convert_column_to_unicode


[docs] class TokenContainer(MethodsMixin, db.Model): """ The "Tokencontainer" table contains the containers and their associated tokens. """ __tablename__ = 'tokencontainer' id = db.Column("id", db.Integer, db.Identity(), primary_key=True) type = db.Column(db.Unicode(100), default='Generic', nullable=False) description = db.Column(db.Unicode(1024), default='') tokens = db.relationship('Token', secondary='tokencontainertoken', back_populates='container') serial = db.Column(db.Unicode(40), default='', unique=True, nullable=False, index=True) owners = db.relationship('TokenContainerOwner', lazy='dynamic', back_populates='container', cascade="all, delete-orphan") last_seen = db.Column(db.DateTime, default=None) last_updated = db.Column(db.DateTime, default=None) states = db.relationship('TokenContainerStates', lazy='dynamic', back_populates='container', cascade="all, delete-orphan") info_list = db.relationship('TokenContainerInfo', lazy='select', back_populates='container', cascade="all, delete-orphan") realms = db.relationship('Realm', secondary='tokencontainerrealm', back_populates='container') template_id = db.Column(db.ForeignKey('tokencontainertemplate.id', name="tokencontainertemplate_id")) template = db.relationship('TokenContainerTemplate', back_populates='containers') def __init__(self, serial, container_type="Generic", tokens=None, description="", states=None): self.serial = serial self.type = container_type self.description = description if tokens: self.tokens = [t.token for t in tokens] if states: self.states = states
[docs] def set_info(self, info): """ Set the additional container info for this container Entries that end with ".type" are used as type for the keys. I.e. two entries sshkey="XYZ" and sshkey.type="password" will store the key sshkey as type "password". :param info: The key-values to set for this container :type info: dict """ if not self.id: # If there is no ID to reference the container, we need to save the # container self.save() types = {} for k, v in info.items(): if k and k.endswith(".type"): types[".".join(k.split(".")[:-1])] = v for k, v in info.items(): if k and not k.endswith(".type"): TokenContainerInfo(self.id, k, v, type=types.get(k)).save(persistent=False) db.session.commit()
[docs] class TokenContainerOwner(MethodsMixin, db.Model): __tablename__ = 'tokencontainerowner' id = db.Column("id", db.Integer, db.Identity(), primary_key=True) container_id = db.Column(db.Integer(), db.ForeignKey("tokencontainer.id")) container = db.relationship('TokenContainer', back_populates='owners') resolver = db.Column(db.Unicode(120), default='', index=True) user_id = db.Column(db.Unicode(320), default='', index=True) realm_id = db.Column(db.Integer(), db.ForeignKey('realm.id')) realm = db.relationship('Realm', lazy='joined', backref='tokencontainerowners') def __init__(self, container_id=None, container_serial=None, resolver=None, user_id=None, realm_id=None, realm_name=None): """ Create a new TokenContainerOwner assignment. :param container_id: :param container_serial: alternative to container_id :param resolver: :param user_id: :param realm_id: :param realm_name: alternative to realm_id """ if realm_id is not None: self.realm_id = realm_id elif realm_name: realm = Realm.query.filter_by(name=realm_name).first() self.realm_id = realm.id if container_id is not None: self.container_id = container_id elif container_serial: container = TokenContainer.query.filter_by(serial=container_serial).first() self.container_id = container.id self.resolver = resolver self.user_id = user_id def save(self, persistent=True): to_func = TokenContainerOwner.query.filter_by(container_id=self.container_id, user_id=self.user_id, realm_id=self.realm_id, resolver=self.resolver).first to = to_func() if to is None: # This very assignment does not exist, yet: db.session.add(self) db.session.commit() if get_app_config_value(SAFE_STORE, False): to = to_func() ret = to.id else: ret = self.id else: ret = to.id # There is nothing to update if persistent: db.session.commit() return ret
[docs] class TokenContainerStates(MethodsMixin, db.Model): __tablename__ = 'tokencontainerstates' id = db.Column("id", db.Integer, db.Identity(), primary_key=True) container_id = db.Column(db.Integer(), db.ForeignKey("tokencontainer.id")) container = db.relationship("TokenContainer", back_populates="states") state = db.Column(db.Unicode(100), default='active', nullable=False) """ The table "tokencontainerstates" is used to store the states of the container. A container can be in several states. """ def __init__(self, container_id=None, state="active"): self.container_id = container_id self.state = state
[docs] class TokenContainerInfo(MethodsMixin, db.Model): """ The table "tokencontainerinfo" is used to store additional, long information that is specific to the containertype. The tokencontainerinfo is reference by the foreign key to the "tokencontainer" table. """ __tablename__ = 'tokencontainerinfo' id = db.Column(db.Integer, db.Identity(), primary_key=True) key = db.Column(db.Unicode(255), nullable=False) value = db.Column(db.UnicodeText(), default='') type = db.Column(db.Unicode(100), default='') description = db.Column(db.Unicode(2000), default='') container_id = db.Column(db.Integer(), db.ForeignKey('tokencontainer.id'), index=True) container = db.relationship('TokenContainer', back_populates='info_list') __table_args__ = (db.UniqueConstraint('container_id', 'key', name='container_id_constraint'),) def __init__(self, container_id, key, value, type=None, description=None): """ Create a new tokencontainerinfo for a given token_id """ self.container_id = container_id self.key = key self.value = convert_column_to_unicode(value) self.type = type self.description = description def save(self, persistent=True): ti_func = TokenContainerInfo.query.filter_by(container_id=self.container_id, key=self.key).first ti = ti_func() if ti is None: # create a new one db.session.add(self) db.session.commit() if get_app_config_value(SAFE_STORE, False): ti = ti_func() ret = ti.id else: ret = self.id else: # update TokenContainerInfo.query.filter_by(container_id=self.container_id, key=self.key).update({'value': self.value, 'description': self.description, 'type': self.type}) ret = ti.id if persistent: db.session.commit() return ret
[docs] class TokenContainerRealm(MethodsMixin, db.Model): """ This table stores to which realms a container is assigned. A container is in the realm of the user it is assigned to. But a container can also be put into many additional realms. """ __tablename__ = 'tokencontainerrealm' container_id = db.Column(db.Integer(), db.ForeignKey("tokencontainer.id"), primary_key=True) realm_id = db.Column(db.Integer(), db.ForeignKey('realm.id'), primary_key=True)
[docs] class TokenContainerTemplate(MethodsMixin, db.Model): __tablename__ = 'tokencontainertemplate' id = db.Column("id", db.Integer, db.Identity(), primary_key=True) options = db.Column(db.Unicode(2000), default='') name = db.Column(db.Unicode(200), default='') container_type = db.Column(db.Unicode(100), default='generic', nullable=False) default = db.Column(db.Boolean, default=False, nullable=False) containers = db.relationship('TokenContainer', back_populates='template') def __init__(self, name, container_type="generic", options='', default=False): self.name = name self.container_type = container_type self.options = options self.default = default
[docs] class TokenContainerToken(MethodsMixin, db.Model): """ Association table to link tokens to containers. """ __tablename__ = 'tokencontainertoken' token_id = db.Column('token_id', db.Integer, db.ForeignKey('token.id'), primary_key=True) container_id = db.Column('container_id', db.Integer, db.ForeignKey('tokencontainer.id'), primary_key=True)