예제 #1
0
def test_setup_db_erase_all(app, engine, capsys, erase):
    app.echo = Echo(verbosity=1)

    # GIVEN a database with records
    app.cli_cmd = "init-database"
    setup_db(app)
    init_db_tables(app, drop_data=True, add_defaults=True)

    KEY = "linotp.foobar"
    item = Config(Key=KEY, Value="123", Type="int", Description="test item")
    db.session.add(item)
    db.session.commit()
    assert db.session.query(Config).filter_by(Key=KEY).count() == 1
    db.session.remove()

    # WHEN I invoke `setup_db`
    setup_db(app)
    init_db_tables(app, drop_data=erase, add_defaults=False)

    if erase:
        # Additional record should have disappeared
        assert db.session.query(Config).filter_by(Key=KEY).count() == 0
    else:
        # Additional record should still be there
        assert db.session.query(Config).filter_by(Key=KEY).count() == 1

        item = db.session.query(Config).filter_by(Key=KEY).first()
        db.session.delete(item)
        db.session.commit()
예제 #2
0
def init_db_command(erase_all_data):
    """
    Create new tables

    The database is initialized and optionally data is cleared.
    """

    if erase_all_data:
        info = 'Recreating database'
    else:
        info = 'Creating database'

    current_app.echo(info, v=1)
    try:
        # Even though we skip initialising the database when doing
        # `linotp init …`, at this point we do need a database engine
        # after all.
        current_app.cli_cmd = 'init-database'  # anything but `init`
        setup_db(current_app)
        init_db_tables(current_app, erase_all_data)
    except Exception as exx:
        current_app.echo(f'Failed to create database: {exx!s}')
        raise sys.exit(1)
    current_app.echo('Database created', v=1)
예제 #3
0
def base_app(tmp_path, request, sqlalchemy_uri, key_directory):
    """
    App instance without context

    Creates and returns a bare app. If you wish
    an app with an initialised application context,
    use the `app` fixture instead
    """

    db_fd, db_path = None, None

    try:

        # ------------------------------------------------------------------ --

        # if sqlalchemy_uri is the fallback, establish a temp file

        if sqlalchemy_uri == "sqlite:///{}":
            db_fd, db_path = tempfile.mkstemp()
            sqlalchemy_uri = sqlalchemy_uri.format(db_path)

        # ------------------------------------------------------------------ --

        # Skip test if incompatible with sqlite

        if sqlalchemy_uri.startswith("sqlite:"):

            if request.node.get_closest_marker("exclude_sqlite"):
                pytest.skip("non sqlite database required for test")

        # ------------------------------------------------------------------ --

        # create the app with common test config

        base_app_config = dict(
            ENV="testing",  # doesn't make a huge difference for us
            TESTING=True,
            DATABASE_URI=sqlalchemy_uri,
            AUDIT_DATABASE_URI="SHARED",
            SQLALCHEMY_TRACK_MODIFICATIONS=False,
            ROOT_DIR=tmp_path,
            CACHE_DIR=tmp_path / "cache",
            DATA_DIR=tmp_path / "data",
            LOG_FILE_DIR=tmp_path / "logs",
            AUDIT_PUBLIC_KEY_FILE=key_directory / "audit-public.pem",
            AUDIT_PRIVATE_KEY_FILE=key_directory / "audit-private.pem",
            SECRET_FILE=key_directory / "encKey",
            LOGGING_LEVEL="DEBUG",
            LOGGING_CONSOLE_LEVEL="DEBUG",
            DISABLE_CONTROLLERS="",
        )

        config = request.node.get_closest_marker("app_config")
        if config is not None:
            base_app_config.update(config.args[0])

        os.environ["LINOTP_CFG"] = ""

        # Pre-generate the important directories
        for key in ("CACHE_DIR", "DATA_DIR", "LOG_FILE_DIR"):
            os.makedirs(base_app_config[key], mode=0o770, exist_ok=True)

        # -----------------------------------------------------------------------

        # Fake running `linotp init enc-key`
        secret_file = base_app_config["SECRET_FILE"]
        if not os.path.exists(secret_file):
            sec_key = 3 * "0123456789abcdef" * 4
            create_secret_key(filename=secret_file, data=sec_key)

        # Fake running `linotp init audit-keys`
        audit_private_key_file = str(base_app_config["AUDIT_PRIVATE_KEY_FILE"])
        if not os.path.exists(audit_private_key_file):
            create_audit_keys(
                audit_private_key_file,
                str(base_app_config["AUDIT_PUBLIC_KEY_FILE"]),
            )

        # -----------------------------------------------------------------------

        os.environ["LINOTP_CMD"] = "init-database"
        app = create_app("testing", base_app_config)

        # Fake running `linotp init database`
        with app.app_context():
            init_db_tables(app, drop_data=True, add_defaults=True)

        yield app

    finally:

        # ------------------------------------------------------------------ --

        # in case of sqlite tempfile fallback, we have to wipe the dishes here

        if db_fd:
            os.close(db_fd)

        if db_path:
            os.unlink(db_path)
예제 #4
0
파일: conftest.py 프로젝트: ppires/LinOTP
def base_app(tmp_path, request, sqlalchemy_uri, key_directory):
    """
    App instance without context

    Creates and returns a bare app. If you wish
    an app with an initialised application context,
    use the `app` fixture instead
    """

    db_fd, db_path = None, None

    try:

        # ------------------------------------------------------------------ --

        # if sqlalchemy_uri is the fallback, establish a temp file

        if sqlalchemy_uri == 'sqlite:///{}':
            db_fd, db_path = tempfile.mkstemp()
            sqlalchemy_uri = sqlalchemy_uri.format(db_path)

        # ------------------------------------------------------------------ --

        # Skip test if incompatible with sqlite

        if sqlalchemy_uri.startswith("sqlite:"):

            if request.node.get_closest_marker('exclude_sqlite'):
                pytest.skip("non sqlite database required for test")

        # ------------------------------------------------------------------ --

        # create the app with common test config

        base_app_config = dict(
            ENV='testing',  # doesn't make a huge difference for us
            TESTING=True,
            DATABASE_URI=sqlalchemy_uri,
            SQLALCHEMY_TRACK_MODIFICATIONS=False,
            ROOT_DIR=tmp_path,
            CACHE_DIR=tmp_path / "cache",
            DATA_DIR=tmp_path / "data",
            LOGFILE_DIR=tmp_path / "logs",
            AUDIT_PUBLIC_KEY_FILE=key_directory / "audit-public.pem",
            AUDIT_PRIVATE_KEY_FILE=key_directory / "audit-private.pem",
            SECRET_FILE=key_directory / "encKey",
            LOGGING_LEVEL="DEBUG",
            LOGGING_CONSOLE_LEVEL="DEBUG",
        )

        config = request.node.get_closest_marker("app_config")
        if config is not None:
            base_app_config.update(config.args[0])

        os.environ["LINOTP_CFG"] = ""

        # Pre-generate the important directories
        for key in ('CACHE_DIR', 'DATA_DIR', 'LOGFILE_DIR'):
            os.makedirs(base_app_config[key], mode=0o770, exist_ok=True)

        # Create secrete key / audit key if necessary.
        with mock.patch('linotp.cli.init_cmd.current_app') as mock_app:
            # The cli commands use current_app.echo to display messages, but this
            # fails here because there no context yet. So we temporary route
            # echo to plain print.
            mock_app.echo = Echo()

            # Fake running `linotp init enc-key`
            secret_file = base_app_config['SECRET_FILE']
            if not os.path.exists(secret_file):
                create_secret_key(filename=secret_file)

            # Fake running `linotp init audit-keys`
            audit_private_key_file = str(
                base_app_config['AUDIT_PRIVATE_KEY_FILE'])
            if not os.path.exists(audit_private_key_file):
                create_audit_keys(
                    audit_private_key_file,
                    str(base_app_config['AUDIT_PUBLIC_KEY_FILE']))

        os.environ["LINOTP_CMD"] = "init-database"
        app = create_app('testing', base_app_config)

        # Fake running `linotp init database`
        with app.app_context():
            init_db_tables(app, drop_data=False, add_defaults=True)

        yield app

    finally:

        # ------------------------------------------------------------------ --

        # in case of sqlite tempfile fallback, we have to wipe the dishes here

        if db_fd:
            os.close(db_fd)

        if db_path:
            os.unlink(db_path)
예제 #5
0
def test_padding_migration(app, base_app, engine):
    """Check that the padding migration has changed

    0. create an empty database
    1. set the security module to the old padding, and
    2. set the db schema version to lower than the padding migration
        version and generate an encrypted value with the old padding
        and store both entries
    3. restore the original padding function
    4. run the db_init, which now re-encryptes the values

    the stored encrypted values must now be different, while the decrypted
    values should be the same

    """

    from linotp.lib.security.default import DefaultSecurityModule

    class MockSecurityModule(DefaultSecurityModule):
        @staticmethod
        def old_padd_data(input_data):
            data = b"\x01\x02"
            padding = (16 - len(input_data + data) % 16) % 16
            return input_data + data + padding * b"\0"

    app.echo = Echo(verbosity=1)

    # we need a base_app context which contains the security provider that is
    # needed for the re-encryption during the 3.1.0.0 migration

    with base_app.app_context():

        # GIVEN a database with records
        app.cli_cmd = "init-database"

        # 0. drop all data and add defaults
        setup_db(app)
        init_db_tables(app, drop_data=True, add_defaults=False)

        # 1. set the security module to the old padding

        sec_provider = base_app.security_provider
        sec_module = sec_provider.security_modules[sec_provider.activeOne]
        sec_module.padd_data = MockSecurityModule.old_padd_data

        # 2.
        # set the db schema version to lower than the padding migration
        # version and generate an encrypted value with the old padding
        # and store both entries

        db_schema_version = "linotp.sql_data_model_version"
        db.session.query(Config).filter_by(Key=db_schema_version).delete()

        item = Config(
            Key=db_schema_version,
            Value="3.0.0.0",
            Type="",
            Description="db schema version",
        )
        db.session.add(item)
        item = Config(
            Key="linotp.Config",
            Value="2021-08-25 11:26:13.101147",
            Type="",
            Description="db config change time stamp",
        )
        db.session.add(item)

        value = "Test123Test123Test123Test123Test123"
        enc_value = sec_module.encryptPassword(value.encode("utf-8"))

        enc_data_key = "linotp.padding_migration_test_password"
        enc_item = Config(
            Key=enc_data_key,
            Value=enc_value,
            Type="encrypted_data",
            Description="migration test password",
        )
        db.session.add(enc_item)

        pw_token = Token(serial="new_pw_token")
        pw_token.LinOtpTokenType = "pw"
        pw_token.LinOtpKeyIV = binascii.hexlify(b":1:")
        pw_token.LinOtpKeyEnc = binascii.hexlify(
            b"$6$XjPTQ1cdb8xFEdnF$m.XoQ//RSPABWGym7o9aPx/.RS1ZySekGBDW7wu"
            b"TZlCDhEM7nf7aOjp03Erk1UFX2OiOhKqaXBMw0a.o4Sbev.")
        db.session.add(pw_token)

        hmac_token = Token(serial="new_hmac_token")
        hmac_token.LinOtpTokenType = "hmac"

        crypted_value = sec_module.encryptPin(
            cryptPin=b"01234567890123456789012345678901")
        iv, _, enc_key = crypted_value.partition(":")
        hmac_token.LinOtpKeyIV = iv.encode("utf-8")
        hmac_token.LinOtpKeyEnc = enc_key.encode("utf-8")

        db.session.add(hmac_token)

        db.session.commit()
        db.session.remove()

        # 3. restore the original padding function

        sec_module.padd_data = DefaultSecurityModule.padd_data

        # 4. run the db_init, which now re-encrypts the values

        setup_db(app)
        init_db_tables(app, drop_data=False, add_defaults=False)

        # verify the expected behavior

        new_enc = db.session.query(Config).filter_by(Key=enc_data_key).first()

        assert new_enc.Value != enc_value

        new_value = sec_module.decryptPassword(new_enc.Value).decode("utf-8")

        assert new_value == value