예제 #1
0
def test_get_constant_raise_not_set_when_requested():
    settings = {"magpie.not_set_but_exists": None}
    with pytest.raises(ValueError):
        c.get_constant("MAGPIE_NOT_SET_BUT_EXISTS", settings, raise_not_set=True)
    with pytest.raises(ValueError):
        c.get_constant("magpie.not_set_but_exists", settings, raise_not_set=True)

    try:
        value = c.get_constant("MAGPIE_NOT_SET_BUT_EXISTS", settings, raise_not_set=False)
        assert value is None
    except LookupError:
        pytest.fail(msg="Should not have raised although constant is not set.")
    try:
        value = c.get_constant("magpie.not_set_but_exists", settings, raise_not_set=False)
        assert value is None
    except LookupError:
        pytest.fail(msg="Should not have raised although constant is not set.")
예제 #2
0
def run_migrations_online(connection=None):
    """
    Run migrations in 'online' mode.

    In this scenario we need to create an Engine and associate a connection with the context.
    """

    url = get_db_url()

    def connect(c=None):
        if isinstance(c, Connection) and not c.closed:
            return c
        if not isinstance(c, Connectable):
            c = create_engine(url, convert_unicode=True, echo=False)
        return c.connect()

    if not database_exists(url):
        db_name = get_constant('MAGPIE_POSTGRES_DB')
        LOGGER.warning(
            'Database [{}] not found, attempting creation...'.format(db_name))
        connection = create_database(url,
                                     encoding='utf8',
                                     template='template0')

    # retry connection and run migration
    with connect(connection) as migrate_conn:
        try:
            context.configure(connection=migrate_conn,
                              target_metadata=target_metadata,
                              version_table='alembic_version',
                              transaction_per_migration=True,
                              render_as_batch=True)
            with context.begin_transaction():
                context.run_migrations()
        finally:
            # close the connection only if not given argument
            if migrate_conn is not connection:
                migrate_conn.close()
예제 #3
0
def setup_cron_logger(log_level=logging.INFO):
    # type: (Union[AnyStr, int]) -> None

    if not isinstance(log_level, int):
        log_level = logging.getLevelName(log_level)

    log_path = constants.get_constant("MAGPIE_CRON_LOG")
    log_path = os.path.expandvars(log_path)
    log_path = os.path.expanduser(log_path)

    stream_handler = logging.StreamHandler(sys.stdout)
    stream_handler.setLevel(log_level)
    file_handler = logging.FileHandler(log_path)
    file_handler.setLevel(log_level)

    formatter = logging.Formatter("%(asctime)s %(levelname)8s %(message)s")
    file_handler.setFormatter(formatter)
    stream_handler.setFormatter(formatter)

    LOGGER.addHandler(file_handler)
    LOGGER.addHandler(stream_handler)

    LOGGER.setLevel(log_level)
예제 #4
0
def get_magpie_url(container=None):
    # type: (Optional[AnySettingsContainer]) -> Str
    if container is None:
        LOGGER.warning(
            "Registry not specified, trying to find Magpie URL from environment"
        )
        url = get_constant("MAGPIE_URL",
                           raise_missing=False,
                           raise_not_set=False,
                           print_missing=False)
        if url:
            return url
        hostname = (
            get_constant("HOSTNAME", raise_not_set=False, raise_missing=False)
            or get_constant(
                "MAGPIE_HOST", raise_not_set=False, raise_missing=False))
        if not hostname:
            raise ConfigurationError(
                "Missing or unset MAGPIE_HOST or HOSTNAME value.")
        magpie_port = get_constant("MAGPIE_PORT", raise_not_set=False)
        magpie_scheme = get_constant("MAGPIE_SCHEME",
                                     raise_not_set=False,
                                     raise_missing=False,
                                     default_value="http")
        return "{}://{}{}".format(
            magpie_scheme, hostname,
            ":{}".format(magpie_port) if magpie_port else "")
    try:
        # add "http" scheme to url if omitted from config since further 'requests' calls fail without it
        # mostly for testing when only "localhost" is specified
        # otherwise config should explicitly define it with 'MAGPIE_URL' env or 'magpie.url' config
        settings = get_settings(container)
        url_parsed = urlparse(
            get_constant("MAGPIE_URL", settings, "magpie.url").strip("/"))
        if url_parsed.scheme in ["http", "https"]:
            return url_parsed.geturl()
        magpie_url = "http://{}".format(url_parsed.geturl())
        print_log(
            "Missing scheme from settings URL, new value: '{}'".format(
                magpie_url), LOGGER, logging.WARNING)
        return magpie_url
    except AttributeError:
        # If magpie.url does not exist, calling strip fct over None will raise this issue
        raise ConfigurationError(
            "MAGPIE_URL or magpie.url config cannot be found")
예제 #5
0
def register_default_users(db_session=None, settings=None):
    # type: (Optional[Session], Optional[AnySettingsContainer]) -> None
    """
    Registers in db every undefined default users and groups matching following variables :

    - ``MAGPIE_ANONYMOUS_USER``
    - ``MAGPIE_USERS_GROUP``
    - ``MAGPIE_ADMIN_GROUP``
    - ``MAGPIE_ADMIN_USER``
    """
    if not isinstance(db_session, Session):
        ini_file_path = get_constant("MAGPIE_INI_FILE_PATH",
                                     settings_container=settings)
        db_session = db.get_db_session_from_config_ini(ini_file_path)
    if not db.is_database_ready(db_session):
        time.sleep(2)
        raise_log("Database not ready")

    init_admin(db_session, settings)
    init_anonymous(db_session, settings)
    init_users_group(db_session, settings)
    transaction.commit()
    db_session.close()
예제 #6
0
 def setUpClass(cls):
     cls.usr = get_constant("MAGPIE_TEST_ADMIN_USERNAME")
     cls.pwd = get_constant("MAGPIE_TEST_ADMIN_PASSWORD")
     cls.url = get_constant("MAGPIE_TEST_REMOTE_SERVER_URL")
     cls.headers, cls.cookies = utils.check_or_try_login_user(
         cls.url, cls.usr, cls.pwd)
     cls.require = "cannot run tests without logged in '{}' user".format(
         get_constant("MAGPIE_ADMIN_GROUP"))
     cls.json_headers = utils.get_headers(cls.url, {
         "Accept": CONTENT_TYPE_JSON,
         "Content-Type": CONTENT_TYPE_JSON
     })
     cls.check_requirements()
     cls.version = utils.TestSetup.get_Version(cls)
     cls.test_user = get_constant("MAGPIE_ANONYMOUS_USER")
     cls.test_group = get_constant("MAGPIE_ANONYMOUS_GROUP")
     cls.test_service_type = utils.get_service_types_for_version(
         cls.version)[0]
     cls.test_service_name = utils.TestSetup.get_AnyServiceOfTestServiceType(
         cls)["service_name"]
예제 #7
0
 def setUpClass(cls):
     cls.url = get_constant("MAGPIE_TEST_REMOTE_SERVER_URL")
     cls.version = utils.TestSetup.get_Version(cls)
     # note: admin credentials to setup data on test instance as needed, but not to be used for these tests
     cls.grp = get_constant("MAGPIE_ADMIN_GROUP")
     cls.usr = get_constant("MAGPIE_TEST_ADMIN_USERNAME",
                            raise_missing=False,
                            raise_not_set=False)
     cls.pwd = get_constant("MAGPIE_TEST_ADMIN_PASSWORD",
                            raise_missing=False,
                            raise_not_set=False)
     cls.setup_admin()
     cls.test_user_name = get_constant(
         "MAGPIE_TEST_USER",
         default_value="unittest-no-auth_ui-user-remote",
         raise_missing=False,
         raise_not_set=False)
     cls.test_group_name = get_constant(
         "MAGPIE_TEST_GROUP",
         default_value="unittest-no-auth_ui-group-remote",
         raise_missing=False,
         raise_not_set=False)
     cls.test_service_type = ServiceWPS.service_type
     cls.test_service_name = "magpie-unittest-service-wps"
예제 #8
0
 def setUpClass(cls):
     cls.url = get_constant('MAGPIE_TEST_REMOTE_SERVER_URL')
예제 #9
0
def edit_group_view(request):
    """
    Update a group by name.
    """
    group = ar.get_group_matchdict_checked(request,
                                           group_name_key="group_name")
    special_groups = [
        get_constant("MAGPIE_ANONYMOUS_GROUP", settings_container=request),
        get_constant("MAGPIE_ADMIN_GROUP", settings_container=request),
    ]
    ax.verify_param(
        group.group_name,
        not_in=True,
        param_compare=special_groups,
        param_name="group_name",
        http_error=HTTPForbidden,
        msg_on_fail=s.Group_PATCH_ReservedKeyword_ForbiddenResponseSchema.
        description)

    new_group_name = ar.get_multiformat_body(request, "group_name")
    new_description = ar.get_multiformat_body(request, "description")
    new_discoverability = ar.get_multiformat_body(request, "discoverable")
    if new_discoverability is not None:
        new_discoverability = asbool(new_discoverability)
    update_name = group.group_name != new_group_name and new_group_name is not None
    update_desc = group.description != new_description and new_description is not None
    update_disc = group.discoverable != new_discoverability and new_discoverability is not None
    ax.verify_param(
        any([update_name, update_desc, update_disc]),
        is_true=True,
        with_param=False,  # params are not useful in response for this case
        http_error=HTTPBadRequest,
        content={"group_name": group.group_name},
        msg_on_fail=s.Group_PATCH_None_BadRequestResponseSchema.description)
    if update_name:
        ax.verify_param(new_group_name,
                        not_none=True,
                        not_empty=True,
                        http_error=HTTPBadRequest,
                        msg_on_fail=s.
                        Group_PATCH_Name_BadRequestResponseSchema.description)
        group_name_size_range = range(
            1, 1 + get_constant("MAGPIE_GROUP_NAME_MAX_LENGTH",
                                settings_container=request))
        ax.verify_param(len(new_group_name),
                        is_in=True,
                        param_compare=group_name_size_range,
                        http_error=HTTPBadRequest,
                        msg_on_fail=s.
                        Group_PATCH_Size_BadRequestResponseSchema.description)
        ax.verify_param(
            GroupService.by_group_name(new_group_name, db_session=request.db),
            is_none=True,
            http_error=HTTPConflict,
            with_param=False,  # don't return group as value
            msg_on_fail=s.Group_PATCH_ConflictResponseSchema.description)
        group.group_name = new_group_name
    if update_desc:
        group.description = new_description
    if update_disc:
        group.discoverable = new_discoverability
    return ax.valid_http(http_success=HTTPOk,
                         detail=s.Group_PATCH_OkResponseSchema.description)
예제 #10
0
def create_user(user_name, password, email, group_name, db_session):
    # type: (Str, Optional[Str], Str, Str, Session) -> HTTPException
    """
    Creates a user if it is permitted and not conflicting. Password must be set to `None` if using external identity.

    Created user will be part of group matching ``group_name`` (can be ``MAGPIE_ANONYMOUS_GROUP`` for minimal access).
    Furthermore, the user will also *always* be associated with ``MAGPIE_ANONYMOUS_GROUP`` (if not already explicitly
    requested with ``group_name``) to allow access to resources with public permission. The ``group_name`` **must**
    be an existing group.

    :returns: valid HTTP response on successful operation.
    """
    def _get_group(grp_name):
        # type: (Str) -> models.Group
        grp = ax.evaluate_call(
            lambda: GroupService.by_group_name(grp_name, db_session=db_session
                                               ),
            httpError=HTTPForbidden,
            msgOnFail=s.UserGroup_GET_ForbiddenResponseSchema.description)
        ax.verify_param(
            grp,
            notNone=True,
            httpError=HTTPBadRequest,
            msgOnFail=s.UserGroup_Check_BadRequestResponseSchema.description)
        return grp

    # Check that group already exists
    group_checked = _get_group(group_name)

    # Check if user already exists
    user_checked = ax.evaluate_call(
        lambda: UserService.by_user_name(user_name=user_name,
                                         db_session=db_session),
        httpError=HTTPForbidden,
        msgOnFail=s.User_Check_ForbiddenResponseSchema.description)
    ax.verify_param(user_checked,
                    isNone=True,
                    httpError=HTTPConflict,
                    msgOnFail=s.User_Check_ConflictResponseSchema.description)

    # Create user with specified name and group to assign
    # noinspection PyArgumentList
    new_user = models.User(user_name=user_name, email=email)
    if password:
        UserService.set_password(new_user, password)
        UserService.regenerate_security_code(new_user)
    ax.evaluate_call(
        lambda: db_session.add(new_user),
        fallback=lambda: db_session.rollback(),
        httpError=HTTPForbidden,
        msgOnFail=s.Users_POST_ForbiddenResponseSchema.description)
    # Fetch user to update fields
    new_user = ax.evaluate_call(
        lambda: UserService.by_user_name(user_name, db_session=db_session),
        httpError=HTTPForbidden,
        msgOnFail=s.UserNew_POST_ForbiddenResponseSchema.description)

    def _add_to_group(usr, grp):
        # type: (models.User, models.Group) -> None
        # noinspection PyArgumentList
        group_entry = models.UserGroup(group_id=grp.id, user_id=usr.id)
        ax.evaluate_call(
            lambda: db_session.add(group_entry),
            fallback=lambda: db_session.rollback(),
            httpError=HTTPForbidden,
            msgOnFail=s.UserGroup_GET_ForbiddenResponseSchema.description)

    # Assign user to group
    new_user_groups = [group_name]
    _add_to_group(new_user, group_checked)
    # Also add user to anonymous group if not already done
    anonym_grp_name = get_constant("MAGPIE_ANONYMOUS_GROUP")
    if group_checked.group_name != anonym_grp_name:
        _add_to_group(new_user, _get_group(anonym_grp_name))
        new_user_groups.append(anonym_grp_name)

    return ax.valid_http(
        httpSuccess=HTTPCreated,
        detail=s.Users_POST_CreatedResponseSchema.description,
        content={u"user": uf.format_user(new_user, new_user_groups)})
예제 #11
0
def authomatic_config(request=None):

    defaults_config = {
        "popup": True,
    }

    openid_config = {
        "openid": {
            "class_": openid.OpenID,
            "display_name": "OpenID",
        },
    }

    esgf_config = {
        "dkrz": {
            "class_": esgfopenid.ESGFOpenID,
            "hostname": "esgf-data.dkrz.de",
            "provider_url": "https://{hostname}/esgf-idp/openid/{username}",
            "display_name": "DKRZ",
        },
        "ipsl": {
            "class_": esgfopenid.ESGFOpenID,
            "hostname": "esgf-node.ipsl.upmc.fr",
            "display_name": "IPSL",
        },
        # former "badc"
        "ceda": {
            "class_": esgfopenid.ESGFOpenID,
            "hostname": "esgf-index1.ceda.ac.uk",
            "provider_url": "https://{hostname}/openid/{username}",
            "display_name": "CEDA",
        },
        # former "pcmdi"
        "llnl": {
            "class_": esgfopenid.ESGFOpenID,
            "hostname": "esgf-node.llnl.gov",
            "display_name": "LLNL",
        },
        "smhi": {
            "class_": esgfopenid.ESGFOpenID,
            "hostname": "esg-dn1.nsc.liu.se",
            "display_name": "SMHI",
        },
    }

    _get_const_info = dict(raise_missing=False,
                           raise_not_set=False,
                           print_missing=True)
    oauth2_config = {
        "github": {
            "class_":
            oauth2.GitHub,
            "display_name":
            "GitHub",
            "consumer_key":
            get_constant("GITHUB_CLIENT_ID", **_get_const_info),
            "consumer_secret":
            get_constant("GITHUB_CLIENT_SECRET", **_get_const_info),
            "redirect_uri":
            request.application_url if request else None,
            # "redirect_uri": "{}/providers/github/signin".format(request.application_url) if request else None,
            "access_headers": {
                "User-Agent": "Magpie"
            },
            "id":
            provider_id(),
            "_apis": {
                "Get your events":
                ("GET", "https://api.github.com/users/{user.username}/events"),
                "Get your watched repos":
                ("GET", "https://api.github.com/user/subscriptions"),
            },
        },
        "wso2": {
            "class_":
            wso2.WSO2,
            "display_name":
            "WSO2",
            "hostname":
            get_constant("WSO2_HOSTNAME", **_get_const_info),
            "consumer_key":
            get_constant("WSO2_CLIENT_ID", **_get_const_info),
            "consumer_secret":
            get_constant("WSO2_CLIENT_SECRET", **_get_const_info),
            "certificate_file":
            get_constant("WSO2_CERTIFICATE_FILE", **_get_const_info)
            or None,  # replace if == ""
            "ssl_verify":
            asbool(
                get_constant("WSO2_SSL_VERIFY",
                             default_value=True,
                             **_get_const_info)),
            "redirect_uri":
            "{}/providers/wso2/signin".format(request.application_url)
            if request else None,
            "id":
            provider_id(),
        }
    }

    # Concatenate the configs.
    config = {}  # type: JSON
    config.update(oauth2_config)
    config.update(openid_config)
    config.update(esgf_config)
    config["__defaults__"] = defaults_config
    return config
예제 #12
0
def update_user_view(request):
    """
    Update user information by user name.
    """
    user = ar.get_user_matchdict_checked_or_logged(request)
    new_user_name = ar.get_multiformat_body(request,
                                            "user_name",
                                            default=user.user_name)
    new_email = ar.get_multiformat_body(request, "email", default=user.email)
    new_password = ar.get_multiformat_body(request,
                                           "password",
                                           default=user.user_password)

    update_username = user.user_name != new_user_name and new_user_name is not None
    update_password = user.user_password != new_password and new_password is not None
    update_email = user.email != new_email and new_email is not None
    ax.verify_param(
        any([update_username, update_password, update_email]),
        is_true=True,
        with_param=False,  # params are not useful in response for this case
        content={"user_name": user.user_name},
        http_error=HTTPBadRequest,
        msg_on_fail=s.User_PATCH_BadRequestResponseSchema.description)
    # user name change is admin-only operation
    if update_username:
        ax.verify_param(
            get_constant("MAGPIE_ADMIN_GROUP"),
            is_in=True,
            param_compare=uu.get_user_groups_checked(request.user, request.db),
            with_param=False,
            http_error=HTTPForbidden,
            msg_on_fail=s.User_PATCH_ForbiddenResponseSchema.description)

    # logged user updating itself is forbidden if it corresponds to special users
    # cannot edit reserved keywords nor apply them to another user
    forbidden_user_names = [
        get_constant("MAGPIE_ADMIN_USER", request),
        get_constant("MAGPIE_ANONYMOUS_USER", request),
        get_constant("MAGPIE_LOGGED_USER", request),
    ]
    check_user_name_cases = [user.user_name, new_user_name
                             ] if update_username else [user.user_name]
    for check_user_name in check_user_name_cases:
        ax.verify_param(
            check_user_name,
            not_in=True,
            param_compare=forbidden_user_names,
            param_name="user_name",
            http_error=HTTPForbidden,
            content={"user_name": str(check_user_name)},
            msg_on_fail=s.User_PATCH_ForbiddenResponseSchema.description)
    if update_username:
        uu.check_user_info(user_name=new_user_name,
                           check_email=False,
                           check_password=False,
                           check_group=False)
        existing_user = ax.evaluate_call(
            lambda: UserService.by_user_name(new_user_name,
                                             db_session=request.db),
            fallback=lambda: request.db.rollback(),
            http_error=HTTPForbidden,
            msg_on_fail=s.User_PATCH_ForbiddenResponseSchema.description)
        ax.verify_param(
            existing_user,
            is_none=True,
            with_param=False,
            http_error=HTTPConflict,
            msg_on_fail=s.User_PATCH_ConflictResponseSchema.description)
        user.user_name = new_user_name
    if update_email:
        uu.check_user_info(email=new_email,
                           check_name=False,
                           check_password=False,
                           check_group=False)
        user.email = new_email
    if update_password:
        uu.check_user_info(password=new_password,
                           check_name=False,
                           check_email=False,
                           check_group=False)
        UserService.set_password(user, new_password)
        UserService.regenerate_security_code(user)

    return ax.valid_http(http_success=HTTPOk,
                         detail=s.Users_PATCH_OkResponseSchema.description)
예제 #13
0
def test_get_constant_alternative_name():
    settings = {"magpie.test_some_value": "some-value"}
    assert c.get_constant("MAGPIE_TEST_SOME_VALUE", settings) == settings["magpie.test_some_value"]
예제 #14
0
from magpie.api import requests as ar
from magpie.api import schemas as s
from magpie.api.management.user.user_formats import format_user
from magpie.api.management.user.user_utils import create_user
from magpie.constants import get_constant
from magpie.security import authomatic_setup, get_provider_names
from magpie.utils import CONTENT_TYPE_JSON, convert_response, get_logger, get_magpie_url

if TYPE_CHECKING:
    from magpie.typedefs import Session, Str

LOGGER = get_logger(__name__)


# dictionaries of {'provider_id': 'provider_display_name'}
MAGPIE_DEFAULT_PROVIDER = get_constant("MAGPIE_DEFAULT_PROVIDER")
MAGPIE_INTERNAL_PROVIDERS = {MAGPIE_DEFAULT_PROVIDER: MAGPIE_DEFAULT_PROVIDER.capitalize()}
MAGPIE_EXTERNAL_PROVIDERS = get_provider_names()
MAGPIE_PROVIDER_KEYS = list(MAGPIE_INTERNAL_PROVIDERS.keys()) + list(MAGPIE_EXTERNAL_PROVIDERS.keys())


# FIXME: use provider enum
def process_sign_in_external(request, username, provider):
    provider_name = provider.lower()
    if provider_name == "openid":
        query_field = dict(id=username)
    elif provider_name == "github":
        query_field = None
        # query_field = dict(login_field=username)
    elif provider_name == "wso2":
        query_field = {}
예제 #15
0
파일: app.py 프로젝트: kridsadakorn/Magpie
def main(global_config=None, **settings):  # noqa: F811
    """
    This function returns a Pyramid WSGI application.
    """
    import magpie.constants  # pylint: disable=C0415  # avoid circular import

    # override magpie ini if provided with --paste to gunicorn, otherwise use environment variable
    config_env = get_constant("MAGPIE_INI_FILE_PATH",
                              settings,
                              raise_missing=True)
    config_ini = (global_config or {}).get("__file__", config_env)
    if config_ini != config_env:
        magpie.constants.MAGPIE_INI_FILE_PATH = config_ini
        settings["magpie.ini_file_path"] = config_ini

    print_log("Setting up loggers...", LOGGER)
    log_lvl = get_constant("MAGPIE_LOG_LEVEL",
                           settings,
                           "magpie.log_level",
                           default_value="INFO",
                           raise_missing=False,
                           raise_not_set=False,
                           print_missing=True)
    # apply proper value in case it was in ini AND env since up until then, only env was check
    # we want to prioritize the ini definition
    magpie.constants.MAGPIE_LOG_LEVEL = log_lvl
    LOGGER.setLevel(log_lvl)
    sa_settings = set_sqlalchemy_log_level(log_lvl)

    print_log("Looking for db migration requirement...", LOGGER)
    run_database_migration_when_ready(
        settings)  # cannot pass db session as it might not even exist yet!

    # NOTE:
    #   migration can cause sqlalchemy engine to reset its internal logger level, although it is properly set
    #   to 'echo=False' because engines are re-created as needed... (ie: missing db)
    #   apply configs to re-enforce the logging level of `sqlalchemy.engine.base.Engine`"""
    set_sqlalchemy_log_level(log_lvl)
    # fetch db session here, otherwise, any following db engine connection will re-initialize
    # with a new engine class and logging settings don't get re-evaluated/applied
    db_session = get_db_session_from_config_ini(config_ini,
                                                settings_override=sa_settings)

    print_log("Validate settings that require explicit definitions...", LOGGER)
    for req_config in [
            "MAGPIE_SECRET", "MAGPIE_ADMIN_USER", "MAGPIE_ADMIN_PASSWORD"
    ]:
        get_constant(req_config,
                     settings_container=settings,
                     raise_missing=True,
                     raise_not_set=True)

    print_log("Register default users...", LOGGER)
    register_defaults(db_session=db_session, settings=settings)

    print_log("Register service providers...", logger=LOGGER)
    combined_config = get_constant("MAGPIE_CONFIG_PATH",
                                   settings,
                                   default_value=None,
                                   raise_missing=False,
                                   raise_not_set=False,
                                   print_missing=True)
    push_phoenix = asbool(
        get_constant("PHOENIX_PUSH",
                     settings,
                     settings_name="phoenix.push",
                     default_value=False,
                     raise_missing=False,
                     raise_not_set=False,
                     print_missing=True))
    prov_cfg = combined_config or get_constant("MAGPIE_PROVIDERS_CONFIG_PATH",
                                               settings,
                                               default_value="",
                                               raise_missing=False,
                                               raise_not_set=False,
                                               print_missing=True)
    magpie_register_services_from_config(prov_cfg,
                                         push_to_phoenix=push_phoenix,
                                         force_update=True,
                                         disable_getcapabilities=False,
                                         db_session=db_session)

    print_log("Register configuration permissions...", LOGGER)
    perm_cfg = combined_config or get_constant(
        "MAGPIE_PERMISSIONS_CONFIG_PATH",
        settings,
        default_value="",
        raise_missing=False,
        raise_not_set=False,
        print_missing=True)
    magpie_register_permissions_from_config(perm_cfg, db_session=db_session)

    print_log("Running configurations setup...", LOGGER)
    patch_magpie_url(settings)

    # avoid cornice conflicting with magpie exception views
    settings["handle_exceptions"] = False

    config = get_auth_config(settings)
    set_cache_regions_from_settings(settings)

    # don't use scan otherwise modules like 'magpie.adapter' are
    # automatically found and cause import errors on missing packages
    print_log("Including Magpie modules...", LOGGER)
    config.include("magpie")
    # config.scan("magpie")

    print_log("Starting Magpie app...", LOGGER)
    wsgi_app = config.make_wsgi_app()
    return wsgi_app
예제 #16
0
파일: app.py 프로젝트: bird-house/Magpie
def main(global_config=None, **settings):
    """
    This function returns a Pyramid WSGI application.
    """
    config_ini = get_constant("MAGPIE_INI_FILE_PATH", raise_missing=True)

    print_log("Setting up loggers...", LOGGER)
    log_lvl = get_constant("MAGPIE_LOG_LEVEL", settings, "magpie.log_level", default_value="INFO",
                           raise_missing=False, raise_not_set=False, print_missing=True)
    LOGGER.setLevel(log_lvl)
    sa_settings = db.set_sqlalchemy_log_level(log_lvl)

    print_log("Looking for db migration requirement...", LOGGER)
    db.run_database_migration_when_ready(settings)  # cannot pass db session as it might not even exist yet!

    # HACK:
    #   migration can cause sqlalchemy engine to reset its internal logger level, although it is properly set
    #   to 'echo=False' because engines are re-created as needed... (ie: missing db)
    #   apply configs to re-enforce the logging level of `sqlalchemy.engine.base.Engine`"""
    db.set_sqlalchemy_log_level(log_lvl)
    # fetch db session here, otherwise, any following db engine connection will re-initialize
    # with a new engine class and logging settings don't get re-evaluated/applied
    db_session = db.get_db_session_from_config_ini(config_ini, settings_override=sa_settings)

    print_log("Register default users...", LOGGER)
    register_default_users(db_session=db_session, settings=settings)

    print_log("Register configuration providers...", logger=LOGGER)
    push_phoenix = asbool(get_constant("PHOENIX_PUSH", settings, settings_name="magpie.phoenix_push",
                                       raise_missing=False, raise_not_set=False, print_missing=True))
    prov_cfg = get_constant("MAGPIE_PROVIDERS_CONFIG_PATH", default_value="",
                            raise_missing=False, raise_not_set=False, print_missing=True)
    if os.path.isfile(prov_cfg):
        magpie_register_services_from_config(prov_cfg, push_to_phoenix=push_phoenix,
                                             force_update=True, disable_getcapabilities=False, db_session=db_session)
    else:
        print_log("No configuration file found for providers registration, skipping...", LOGGER, logging.WARN)

    print_log("Register configuration permissions...", LOGGER)
    perm_cfg = get_constant("MAGPIE_PERMISSIONS_CONFIG_PATH", default_value="",
                            raise_missing=False, raise_not_set=False, print_missing=True)
    if os.path.isfile(perm_cfg):
        magpie_register_permissions_from_config(get_constant("MAGPIE_PERMISSIONS_CONFIG_PATH"), db_session=db_session)
    else:
        print_log("No configuration file found for permissions registration, skipping...", LOGGER, logging.WARN)

    print_log("Running configurations setup...", LOGGER)
    patch_magpie_url(settings)

    # avoid cornice conflicting with magpie exception views
    settings["handle_exceptions"] = False

    config = get_auth_config(settings)
    set_cache_regions_from_settings(settings)

    # Don't use scan otherwise modules like 'magpie.adapter' are
    # automatically found and cause import errors on missing packages
    config.include("magpie")
    # config.scan("magpie")

    print_log("Starting Magpie app...", LOGGER)
    wsgi_app = config.make_wsgi_app()
    return wsgi_app
예제 #17
0
파일: views.py 프로젝트: bird-house/Magpie
    def edit_user(self):
        user_name = self.request.matchdict["user_name"]
        cur_svc_type = self.request.matchdict["cur_svc_type"]
        inherit_grp_perms = self.request.matchdict.get(
            "inherit_groups_permissions", False)

        own_groups = self.get_user_groups(user_name)
        all_groups = self.get_all_groups(
            first_default_group=get_constant("MAGPIE_USERS_GROUP"))

        # TODO:
        #   Until the api is modified to make it possible to request from the RemoteResource table,
        #   we have to access the database directly here
        session = self.request.db

        try:
            # The service type is "default". This function replaces cur_svc_type with the first service type.
            svc_types, cur_svc_type, services = self.get_services(cur_svc_type)
        except Exception as e:
            raise HTTPBadRequest(detail=repr(e))

        user_path = schemas.UserAPI.path.format(user_name=user_name)
        user_resp = request_api(self.request, user_path, "GET")
        check_response(user_resp)
        user_info = get_json(user_resp)["user"]
        user_info[u"edit_mode"] = u"no_edit"
        user_info[u"own_groups"] = own_groups
        user_info[u"groups"] = all_groups
        user_info[u"inherit_groups_permissions"] = inherit_grp_perms
        error_message = ""

        # In case of update, changes are not reflected when calling
        # get_user_or_group_resources_permissions_dict so we must take care
        # of them
        res_id = None
        removed_perms = None
        new_perms = None

        if self.request.method == "POST":
            res_id = self.request.POST.get(u"resource_id")
            is_edit_group_membership = False
            is_save_user_info = False
            requires_update_name = False

            if u"inherit_groups_permissions" in self.request.POST:
                inherit_grp_perms = asbool(
                    self.request.POST[u"inherit_groups_permissions"])
                user_info[u"inherit_groups_permissions"] = inherit_grp_perms

            if u"delete" in self.request.POST:
                resp = request_api(self.request, user_path, "DELETE")
                check_response(resp)
                return HTTPFound(self.request.route_url("view_users"))
            elif u"goto_service" in self.request.POST:
                return self.goto_service(res_id)
            elif u"clean_resource" in self.request.POST:
                # "clean_resource" must be above "edit_permissions" because they"re in the same form.
                self.delete_resource(res_id)
            elif u"edit_permissions" in self.request.POST:
                if not res_id or res_id == "None":
                    remote_id = int(self.request.POST.get("remote_id"))
                    services_names = [
                        s["service_name"] for s in services.values()
                    ]
                    res_id = self.add_remote_resource(cur_svc_type,
                                                      services_names,
                                                      user_name,
                                                      remote_id,
                                                      is_user=True)

                removed_perms, new_perms = \
                    self.edit_user_or_group_resource_permissions(user_name, res_id, is_user=True)
            elif u"edit_group_membership" in self.request.POST:
                is_edit_group_membership = True
            elif u"edit_username" in self.request.POST:
                user_info[u"edit_mode"] = u"edit_username"
            elif u"edit_password" in self.request.POST:
                user_info[u"edit_mode"] = u"edit_password"
            elif u"edit_email" in self.request.POST:
                user_info[u"edit_mode"] = u"edit_email"
            elif u"save_username" in self.request.POST:
                user_info[u"user_name"] = self.request.POST.get(
                    u"new_user_name")
                is_save_user_info = True
                requires_update_name = True
            elif u"save_password" in self.request.POST:
                user_info[u"password"] = self.request.POST.get(
                    u"new_user_password")
                is_save_user_info = True
            elif u"save_email" in self.request.POST:
                user_info[u"email"] = self.request.POST.get(u"new_user_email")
                is_save_user_info = True
            elif u"force_sync" in self.request.POST:
                errors = []
                for service_info in services.values():
                    # noinspection PyBroadException
                    try:
                        sync_resources.fetch_single_service(
                            service_info["resource_id"], session)
                    except Exception:
                        errors.append(service_info["service_name"])
                if errors:
                    error_message += self.make_sync_error_message(errors)
            elif u"clean_all" in self.request.POST:
                ids_to_clean = self.request.POST.get("ids_to_clean").split(";")
                for id_ in ids_to_clean:
                    self.delete_resource(id_)

            if is_save_user_info:
                resp = request_api(self.request,
                                   user_path,
                                   "PUT",
                                   data=user_info)
                check_response(resp)
                # need to commit updates since we are using the same session
                # otherwise, updated user doesn't exist yet in the db for next calls
                self.request.tm.commit()

            # always remove password from output
            user_info.pop(u"password", None)

            if requires_update_name:
                # re-fetch user groups as current user-group will have changed on new user_name
                user_name = user_info[u"user_name"]
                user_info[u"own_groups"] = self.get_user_groups(user_name)
                # return immediately with updated URL to user with new name
                users_url = self.request.route_url("edit_user",
                                                   user_name=user_name,
                                                   cur_svc_type=cur_svc_type)
                return HTTPMovedPermanently(location=users_url)

            # edits to groups checkboxes
            if is_edit_group_membership:
                selected_groups = self.request.POST.getall("member")
                removed_groups = list(set(own_groups) - set(selected_groups))
                new_groups = list(set(selected_groups) - set(own_groups))
                for group in removed_groups:
                    path = schemas.UserGroupAPI.path.format(
                        user_name=user_name, group_name=group)
                    resp = request_api(self.request, path, "DELETE")
                    check_response(resp)
                for group in new_groups:
                    path = schemas.UserGroupsAPI.path.format(
                        user_name=user_name)
                    data = {"group_name": group}
                    resp = request_api(self.request, path, "POST", data=data)
                    check_response(resp)
                user_info[u"own_groups"] = self.get_user_groups(user_name)

        # display resources permissions per service type tab
        try:
            res_perm_names, res_perms = self.get_user_or_group_resources_permissions_dict(
                user_name,
                services,
                cur_svc_type,
                is_user=True,
                is_inherit_groups_permissions=inherit_grp_perms)
        except Exception as e:
            raise HTTPBadRequest(detail=repr(e))

        if res_id and (removed_perms or new_perms):
            self.update_user_or_group_resources_permissions_dict(
                res_perms, res_id, removed_perms, new_perms)

        sync_types = [s["service_sync_type"] for s in services.values()]
        sync_implemented = any(s in sync_resources.SYNC_SERVICES_TYPES
                               for s in sync_types)

        info = self.get_remote_resources_info(res_perms, services, session)
        res_perms, ids_to_clean, last_sync_humanized, out_of_sync = info

        if out_of_sync:
            error_message = self.make_sync_error_message(out_of_sync)

        user_info[u"error_message"] = error_message
        user_info[u"ids_to_clean"] = ";".join(ids_to_clean)
        user_info[u"last_sync"] = last_sync_humanized
        user_info[u"sync_implemented"] = sync_implemented
        user_info[u"out_of_sync"] = out_of_sync
        user_info[u"cur_svc_type"] = cur_svc_type
        user_info[u"svc_types"] = svc_types
        user_info[u"resources"] = res_perms
        user_info[u"permissions"] = res_perm_names
        return add_template_data(self.request, data=user_info)
예제 #18
0
파일: views.py 프로젝트: bird-house/Magpie
    def add_user(self):
        users_group = get_constant("MAGPIE_USERS_GROUP")
        return_data = {
            u"conflict_group_name": False,
            u"conflict_user_name": False,
            u"conflict_user_email": False,
            u"invalid_user_name": False,
            u"invalid_user_email": False,
            u"invalid_password": False,
            u"too_long_user_name": False,
            u"form_user_name": u"",
            u"form_user_email": u"",
            u"user_groups":
            self.get_all_groups(first_default_group=users_group)
        }
        check_data = [
            u"conflict_group_name", u"conflict_user_name", u"conflict_email",
            u"invalid_user_name", u"invalid_email", u"invalid_password"
        ]

        if "create" in self.request.POST:
            groups = self.get_all_groups()
            user_name = self.request.POST.get("user_name")
            group_name = self.request.POST.get("group_name")
            user_email = self.request.POST.get("email")
            password = self.request.POST.get("password")
            return_data[u"form_user_name"] = user_name
            return_data[u"form_user_email"] = user_email

            if group_name not in groups:
                data = {u"group_name": group_name}
                resp = request_api(self.request,
                                   schemas.GroupsAPI.path,
                                   "POST",
                                   data=data)
                if resp.status_code == HTTPConflict.code:
                    return_data[u"conflict_group_name"] = True
            if user_email in self.get_user_emails():
                return_data[u"conflict_user_email"] = True
            if user_email == "":
                return_data[u"invalid_user_email"] = True
            if len(user_name) > get_constant("MAGPIE_USER_NAME_MAX_LENGTH"):
                return_data[u"too_long_user_name"] = True
            if user_name in self.get_user_names():
                return_data[u"conflict_user_name"] = True
            if user_name == "":
                return_data[u"invalid_user_name"] = True
            if password == "":
                return_data[u"invalid_password"] = True

            for check_fail in check_data:
                if return_data.get(check_fail, False):
                    return add_template_data(self.request, return_data)

            data = {
                u"user_name": user_name,
                u"email": user_email,
                u"password": password,
                u"group_name": group_name
            }
            resp = request_api(self.request,
                               schemas.UsersAPI.path,
                               "POST",
                               data=data)
            check_response(resp)
            return HTTPFound(self.request.route_url("view_users"))

        return add_template_data(self.request, return_data)
예제 #19
0
파일: db.py 프로젝트: kridsadakorn/Magpie
def run_database_migration_when_ready(settings, db_session=None):
    # type: (SettingsType, Optional[Session]) -> None
    """
    Runs db migration if requested by config and need from revisions.
    """
    db_ready = False
    if asbool(
            get_constant("MAGPIE_DB_MIGRATION",
                         settings,
                         "magpie.db_migration",
                         default_value=True,
                         raise_missing=False,
                         raise_not_set=False,
                         print_missing=True)):
        conf_attempts = int(
            get_constant("MAGPIE_DB_MIGRATION_ATTEMPTS",
                         settings,
                         "magpie.db_migration_attempts",
                         default_value=5,
                         raise_missing=False,
                         raise_not_set=False,
                         print_missing=True))

        print_log("Running database migration (as required)...", logger=LOGGER)
        attempts = max(conf_attempts, 1)
        if attempts != conf_attempts:
            print_log(
                "Database migration attempts updated to {}".format(attempts),
                logger=LOGGER,
                level=logging.WARNING)
        for i in range(1, attempts + 1):
            try:
                run_database_migration(db_session=db_session,
                                       settings=settings)
            except ImportError as exc:
                print_log(
                    "Database migration produced [{!r}] (ignored).".format(
                        exc),
                    logger=LOGGER,
                    level=logging.WARNING,
                    exc_info=exc)
            except Exception as exc:
                if i <= attempts:
                    print_log(
                        "Database migration failed [{!r}]. Retrying... ({}/{})"
                        .format(exc, i, attempts),
                        logger=LOGGER,
                        level=logging.WARNING,
                        exc_info=exc)
                    time.sleep(2)
                    continue
                raise_log("Database migration failed [{!r}]".format(exc),
                          exception=RuntimeError,
                          logger=LOGGER)

            db_ready = is_database_ready(db_session)
            if not db_ready:
                if i <= attempts:
                    print_log("Database not ready. Retrying... ({}/{})".format(
                        i, attempts),
                              logger=LOGGER,
                              level=logging.WARNING)
                    time.sleep(2)
                    continue
                print_log(
                    "Database not ready. Maximum attempts reached ({})".format(
                        attempts),
                    logger=LOGGER,
                    level=logging.WARNING)
            break
    else:
        print_log(
            "Database migration skipped as per 'MAGPIE_DB_MIGRATION' requirement...",
            logger=LOGGER)
        db_ready = is_database_ready(db_session)
    if not db_ready:
        raise_log("Database not ready", exception=RuntimeError, logger=LOGGER)
예제 #20
0
def includeme(config):
    from magpie.ui.user.views import UserViews
    LOGGER.info("Adding UI user...")
    path = "/ui/users/{}".format(get_constant("MAGPIE_LOGGED_USER"))
    config.add_route(UserViews.edit_current_user.__name__, path)
    config.scan()
예제 #21
0
def test_get_constant_with_same_name():
    test_value = "test-constant"
    c.MAGPIE_CRON_LOG = test_value
    value = c.get_constant("MAGPIE_CRON_LOG")
    assert value == test_value
예제 #22
0
    def effective_permissions(self,
                              user,
                              resource,
                              permissions=None,
                              allow_match=True):
        # type: (models.User, ServiceOrResourceType, Optional[Collection[Permission]], bool) -> List[PermissionSet]
        """
        Obtains the effective permissions the user has over the specified resource.

        Recursively rewinds the resource tree from the specified resource up to the top-most parent service the resource
        resides under (or directly if the resource is the service) and retrieve permissions along the way that should be
        applied to children when using scoped-resource inheritance. Rewinding of the tree can terminate earlier when
        permissions can be immediately resolved such as when more restrictive conditions enforce denied access.

        Both user and group permission inheritance is resolved simultaneously to tree hierarchy with corresponding
        allow and deny conditions. User :term:`Direct Permissions` have priority over all its groups
        :term:`Inherited Permissions`, and denied permissions have priority over allowed access ones.

        All applicable permissions on the resource (as defined by :meth:`allowed_permissions`) will have their
        resolution (Allow/Deny) provided as output, unless a specific subset of permissions is requested using
        :paramref:`permissions`. Other permissions are ignored in this case to only resolve requested ones.
        For example, this parameter can be used to request only ACL resolution from specific permissions applicable
        for a given request, as obtained by :meth:`permission_requested`.

        Permissions scoped as `match` can be ignored using :paramref:`allow_match`, such as when the targeted resource
        does not exist.

        .. seealso::
            - :meth:`ServiceInterface.resource_requested`
        """
        if not permissions:
            permissions = self.allowed_permissions(resource)
        requested_perms = set(permissions)  # type: Set[Permission]
        effective_perms = dict()  # type: Dict[Permission, PermissionSet]

        # immediately return all permissions if user is an admin
        db_session = self.request.db
        admin_group = get_constant("MAGPIE_ADMIN_GROUP", self.request)
        admin_group = GroupService.by_group_name(admin_group,
                                                 db_session=db_session)
        if admin_group in user.groups:  # noqa
            return [
                PermissionSet(perm,
                              access=Access.ALLOW,
                              scope=Scope.MATCH,
                              typ=PermissionType.EFFECTIVE,
                              reason=PERMISSION_REASON_ADMIN)
                for perm in permissions
            ]

        # level at which last permission was found, -1 if not found
        # employed to resolve with *closest* scope and for applicable 'reason' combination on same level
        effective_level = dict()  # type: Dict[Permission, Optional[int]]
        current_level = 1  # one-based to avoid ``if level:`` check failing with zero
        full_break = False
        # current and parent resource(s) recursive-scope
        while resource is not None and not full_break:  # bottom-up until service is reached

            # include both permissions set in database as well as defined directly on resource
            cur_res_perms = ResourceService.perms_for_user(
                resource, user, db_session=db_session)
            cur_res_perms.extend(permission_to_pyramid_acls(resource.__acl__))

            for perm_name in requested_perms:
                if full_break:
                    break
                for perm_tup in cur_res_perms:
                    perm_set = PermissionSet(perm_tup)

                    # if user is owner (directly or via groups), all permissions are set,
                    # but continue processing this resource until end in case user explicit deny reverts it
                    if perm_tup.perm_name == ALL_PERMISSIONS:
                        # FIXME:
                        #   This block needs to be validated if support of ownership rules are added.
                        #   Conditions must be revised according to wanted behaviour...
                        #   General idea for now is that explict user/group deny should be prioritized over resource
                        #   ownership permissions since these can be attributed to *any user* while explicit deny are
                        #   definitely set by an admin-level user.
                        for perm in requested_perms:
                            if perm_set.access == Access.DENY:
                                all_perm = PermissionSet(
                                    perm, perm_set.access, perm.scope,
                                    PermissionType.OWNED)
                                effective_perms[perm] = all_perm
                            else:
                                all_perm = PermissionSet(
                                    perm, perm_set.access, perm.scope,
                                    PermissionType.OWNED)
                                effective_perms.setdefault(perm, all_perm)
                        full_break = True
                        break
                    # skip if the current permission must not be processed (at all or for the moment until next 'name')
                    if perm_set.name not in requested_perms or perm_set.name != perm_name:
                        continue
                    # only first resource can use match (if even enabled with found one), parents are recursive-only
                    if not allow_match and perm_set.scope == Scope.MATCH:
                        continue
                    # pick the first permission if none was found up to this point
                    prev_perm = effective_perms.get(perm_name)
                    scope_level = effective_level.get(perm_name)
                    if not prev_perm:
                        effective_perms[perm_name] = perm_set
                        effective_level[perm_name] = current_level
                        continue

                    # user direct permissions have priority over inherited ones from groups
                    # if inherited permission was found during previous iteration, override it with direct permission
                    if perm_set.type == PermissionType.DIRECT:
                        # - reset resolution scope of previous permission attributed to group as it takes precedence
                        # - since there can't be more than one user permission-name per resource on a given level,
                        #   scope resolution is done after applying this *closest* permission, ignore higher level ones
                        if prev_perm.type == PermissionType.INHERITED or not scope_level:
                            effective_perms[perm_name] = perm_set
                            effective_level[perm_name] = current_level
                        continue  # final decision for this user, skip any group permissions

                    # resolve prioritized permission according to ALLOW/DENY, scope and group priority
                    # (see 'PermissionSet.resolve' method for extensive details)
                    # skip if last permission is not on group to avoid redundant USER > GROUP check processed before
                    if prev_perm.type == PermissionType.INHERITED:
                        # - If new permission to process is done against the previous permission from *same* tree-level,
                        #   there is a possibility to combine equal priority groups. In such case, reason is 'MULTIPLE'.
                        # - If not of equal priority, the appropriate permission is selected and reason is overridden
                        #   accordingly by the new higher priority permission.
                        # - If no permission was defined at all (first occurrence), also set it using current permission
                        if scope_level in [None, current_level]:
                            resolved_perm = PermissionSet.resolve(
                                perm_set,
                                prev_perm,
                                context=PermissionType.EFFECTIVE)
                            effective_perms[perm_name] = resolved_perm
                            effective_level[perm_name] = current_level
                        # - If new permission is at *different* tree-level, it applies only if the group has higher
                        #   priority than the previous one, to respect the *closest* scope to the target resource.
                        #   Same priorities are ignored as they were already resolved by *closest* scope above.
                        # - Reset scope level with new permission such that another permission of same group priority as
                        #   that could be processed in next iteration can be compared against it, to resolve 'access'
                        #   priority between them.
                        elif perm_set.group_priority > prev_perm.group_priority:
                            effective_perms[perm_name] = perm_set
                            effective_level[perm_name] = current_level

            # don't bother moving to parent if everything is resolved already
            #   can only assume nothing left to resolve if all permissions are direct on user (highest priority)
            #   if any found permission is group inherited, higher level user permission could still override it
            if (len(effective_perms) == len(requested_perms)
                    and all(perm.type == PermissionType.DIRECT
                            for perm in effective_perms.values())):
                break
            # otherwise, move to parent if any available, since we are not done rewinding the resource tree
            allow_match = False  # reset match not applicable anymore for following parent resources
            current_level += 1
            if resource.parent_id:
                resource = ResourceService.by_resource_id(
                    resource.parent_id, db_session=db_session)
            else:
                resource = None

        # set deny for all still unresolved permissions from requested ones
        resolved_perms = set(effective_perms)
        missing_perms = set(permissions) - resolved_perms
        final_perms = set(effective_perms.values())  # type: Set[PermissionSet]
        for perm_name in missing_perms:
            perm = PermissionSet(perm_name,
                                 access=Access.DENY,
                                 scope=Scope.MATCH,
                                 typ=PermissionType.EFFECTIVE,
                                 reason=PERMISSION_REASON_DEFAULT)
            final_perms.add(perm)
        # enforce type and scope (use MATCH to make it explicit that it applies specifically for this resource)
        for perm in final_perms:
            perm.type = PermissionType.EFFECTIVE
            perm.scope = Scope.MATCH
        return list(final_perms)
예제 #23
0
def authomatic_config(request=None):

    defaults_config = {
        'popup': True,
    }

    openid_config = {
        'openid': {
            'class_': openid.OpenID,
            'display_name': 'OpenID',
        },
    }

    esgf_config = {
        'dkrz': {
            'class_': esgfopenid.ESGFOpenID,
            'hostname': 'esgf-data.dkrz.de',
            'provider_url': 'https://{hostname}/esgf-idp/openid/{username}',
            'display_name': 'DKRZ',
        },
        'ipsl': {
            'class_': esgfopenid.ESGFOpenID,
            'hostname': 'providers-node.ipsl.fr',
            'display_name': 'IPSL',
        },
        'badc': {
            'class_': esgfopenid.ESGFOpenID,
            'hostname': 'ceda.ac.uk',
            'provider_url': 'https://{hostname}/openid/{username}',
            'display_name': 'BADC',
        },
        'pcmdi': {
            'class_': esgfopenid.ESGFOpenID,
            'hostname': 'providers-node.llnl.gov',
            'display_name': 'PCMDI',
        },
        'smhi': {
            'class_': esgfopenid.ESGFOpenID,
            'hostname': 'esg-dn1.nsc.liu.se',
            'display_name': 'SMHI',
        },
    }

    _get_const_info = dict(raise_missing=False,
                           raise_not_set=False,
                           print_missing=True)
    oauth2_config = {
        'github': {
            'class_':
            oauth2.GitHub,
            'display_name':
            'GitHub',
            'consumer_key':
            get_constant('GITHUB_CLIENT_ID', **_get_const_info),
            'consumer_secret':
            get_constant('GITHUB_CLIENT_SECRET', **_get_const_info),
            'redirect_uri':
            request.application_url if request else None,
            # 'redirect_uri': '{}/providers/github/signin'.format(request.application_url) if request else None,
            'access_headers': {
                'User-Agent': 'Magpie'
            },
            'id':
            provider_id(),
            '_apis': {
                'Get your events':
                ('GET', 'https://api.github.com/users/{user.username}/events'),
                'Get your watched repos':
                ('GET', 'https://api.github.com/user/subscriptions'),
            },
        },
        'wso2': {
            'class_':
            wso2.WSO2,
            'display_name':
            'WSO2',
            'hostname':
            get_constant('WSO2_HOSTNAME', **_get_const_info),
            'consumer_key':
            get_constant('WSO2_CLIENT_ID', **_get_const_info),
            'consumer_secret':
            get_constant('WSO2_CLIENT_SECRET', **_get_const_info),
            'certificate_file':
            get_constant('WSO2_CERTIFICATE_FILE', **_get_const_info)
            or None,  # replace if == ''
            'ssl_verify':
            asbool(
                get_constant('WSO2_SSL_VERIFY',
                             default_value=True,
                             **_get_const_info)),
            'redirect_uri':
            '{}/providers/wso2/signin'.format(request.application_url)
            if request else None,
            'id':
            provider_id(),
        }
    }

    # Concatenate the configs.
    config = {}  # type: JSON
    config.update(oauth2_config)
    config.update(openid_config)
    config.update(esgf_config)
    config['__defaults__'] = defaults_config
    return config
예제 #24
0
def test_constant_protected_no_override():
    for const_name in c.MAGPIE_CONSTANTS:
        with mock.patch.dict("os.environ", {const_name: "override-value"}):
            const = c.get_constant(const_name)
            assert const != "override-value"
예제 #25
0
def run_batch_update_user_command(test_app, expected_users,
                                  create_command_xargs, delete_command_xargs):
    """
    Tests batch user creation and deletion of the CLI utility.

    Because CLI utility employs requests that cannot be mocked if executed through sub-process, we call it directly.
    """
    test_admin_usr = get_constant("MAGPIE_TEST_ADMIN_USERNAME",
                                  raise_not_set=False,
                                  raise_missing=False)
    test_admin_pwd = get_constant("MAGPIE_TEST_ADMIN_PASSWORD",
                                  raise_not_set=False,
                                  raise_missing=False)
    if not test_admin_usr or not test_admin_pwd:
        test_admin_usr = get_constant("MAGPIE_ADMIN_USER")
        test_admin_pwd = get_constant("MAGPIE_ADMIN_PASSWORD")
    test_url = "http://localhost"

    def mock_request(*args, **kwargs):
        method, url, args = args[0], args[1], args[2:]
        path = url.replace(test_url, "")
        # because CLI utility does multiple login tests, we must force TestApp logout to forget session
        # otherwise, error is raised because of user session mismatch between previous login and new one requested
        if path.startswith("/signin"):
            utils.check_or_try_logout_user(test_app)
        return utils.test_request(test_app, method, path, *args, **kwargs)

    def run_command(operation_name, operation_args):
        with tempfile2.TemporaryDirectory() as tmpdir:
            with mock.patch("requests.Session.request",
                            side_effect=mock_request):
                with mock.patch("requests.request", side_effect=mock_request):
                    cmd = [
                        test_url, test_admin_usr, test_admin_pwd, "-o", tmpdir
                    ] + operation_args
                    assert batch_update_users.main(
                        cmd) == 0, "failed execution due to invalid arguments"
                    assert len(
                        os.listdir(tmpdir)
                    ) == 1, "utility should have produced 1 output file"
                    file = os.path.join(tmpdir, os.listdir(tmpdir)[0])
                    utils.check_val_is_in(operation_name, file)
                    assert os.path.isfile(file)
                    with open(file, "r") as fd:
                        file_text = fd.read()
                    assert all([test_user in file_text for test_user in expected_users]), \
                        "all users should have been processed and logged in output result file"

    # cleanup in case of previous failure
    _, test_admin_cookies = utils.check_or_try_login_user(
        test_app, username=test_admin_usr, password=test_admin_pwd)
    for user in expected_users:
        utils.TestSetup.delete_TestUser(
            test_app,
            override_user_name=user,  # noqa
            override_headers={},
            override_cookies=test_admin_cookies)
    utils.check_or_try_logout_user(test_app)

    # test user creation and validate that users were all created
    run_command("create", create_command_xargs)
    utils.check_or_try_logout_user(test_app)
    _, test_admin_cookies = utils.check_or_try_login_user(
        test_app, username=test_admin_usr, password=test_admin_pwd)
    resp = utils.test_request(test_app,
                              "GET",
                              "/users",
                              cookies=test_admin_cookies)
    body = utils.check_response_basic_info(resp)
    for user in expected_users:
        utils.check_val_is_in(user, body["user_names"])

    # test user deletion and validate users are all deleted
    run_command("delete", ["-d"] + delete_command_xargs)
    utils.check_or_try_logout_user(test_app)
    _, test_admin_cookies = utils.check_or_try_login_user(
        test_app, username=test_admin_usr, password=test_admin_pwd)
    resp = utils.test_request(test_app,
                              "GET",
                              "/users",
                              cookies=test_admin_cookies)
    body = utils.check_response_basic_info(resp)
    for user in expected_users:
        utils.check_val_not_in(user, body["user_names"])
예제 #26
0
def create_user(user_name, password, email, group_name, db_session):
    # type: (Str, Optional[Str], Str, Optional[Str], Session) -> HTTPException
    """
    Creates a user if it is permitted and not conflicting. Password must be set to ``None`` if using external identity.

    Created user will immediately assigned membership to the group matching :paramref:`group_name`
    (can be :py:data:`MAGPIE_ANONYMOUS_GROUP` for minimal access). If no group is provided, this anonymous group will
    be applied by default, creating a user effectively without any permissions other than ones set directly for him.

    Furthermore, the user will also *always* be associated with :py:data:`MAGPIE_ANONYMOUS_GROUP` (if not already
    explicitly or implicitly requested with :paramref:`group_name`) to allow access to resources with public permission.
    Argument :paramref:`group_name` **MUST** be an existing group if provided.

    :returns: valid HTTP response on successful operation.
    """

    def _get_group(grp_name):
        # type: (Str) -> models.Group
        ax.verify_param(grp_name, not_none=True, not_empty=True, matches=True,
                        param_compare=ax.PARAM_REGEX, param_name="group_name",
                        http_error=HTTPBadRequest, msg_on_fail=s.UserGroup_Check_BadRequestResponseSchema.description)
        grp = ax.evaluate_call(lambda: GroupService.by_group_name(grp_name, db_session=db_session),
                               http_error=HTTPForbidden,
                               msg_on_fail=s.UserGroup_GET_ForbiddenResponseSchema.description)
        ax.verify_param(grp, not_none=True, http_error=HTTPNotFound, with_param=False,
                        msg_on_fail=s.UserGroup_Check_NotFoundResponseSchema.description)
        return grp

    # Check that group already exists
    if group_name is None:
        group_name = get_constant("MAGPIE_ANONYMOUS_GROUP")
    is_internal = password is not None
    check_user_info(user_name, email, password, group_name, check_password=is_internal)
    group_checked = _get_group(group_name)

    # Check if user already exists
    user_checked = ax.evaluate_call(lambda: UserService.by_user_name(user_name=user_name, db_session=db_session),
                                    http_error=HTTPForbidden,
                                    msg_on_fail=s.User_Check_ForbiddenResponseSchema.description)
    ax.verify_param(user_checked, is_none=True, with_param=False, http_error=HTTPConflict,
                    msg_on_fail=s.User_Check_ConflictResponseSchema.description)

    # Create user with specified name and group to assign
    new_user = models.User(user_name=user_name, email=email)  # noqa
    if is_internal:
        UserService.set_password(new_user, password)
        UserService.regenerate_security_code(new_user)
    ax.evaluate_call(lambda: db_session.add(new_user), fallback=lambda: db_session.rollback(),
                     http_error=HTTPForbidden, msg_on_fail=s.Users_POST_ForbiddenResponseSchema.description)
    # Fetch user to update fields
    new_user = ax.evaluate_call(lambda: UserService.by_user_name(user_name, db_session=db_session),
                                http_error=HTTPForbidden,
                                msg_on_fail=s.UserNew_POST_ForbiddenResponseSchema.description)

    def _add_to_group(usr, grp):
        # type: (models.User, models.Group) -> None
        group_entry = models.UserGroup(group_id=grp.id, user_id=usr.id)  # noqa
        ax.evaluate_call(lambda: db_session.add(group_entry), fallback=lambda: db_session.rollback(),
                         http_error=HTTPForbidden, msg_on_fail=s.UserGroup_GET_ForbiddenResponseSchema.description)

    # Assign user to group
    new_user_groups = [group_name]
    _add_to_group(new_user, group_checked)
    # Also add user to anonymous group if not already done
    anonym_grp_name = get_constant("MAGPIE_ANONYMOUS_GROUP")
    if group_checked.group_name != anonym_grp_name:
        _add_to_group(new_user, _get_group(anonym_grp_name))
        new_user_groups.append(anonym_grp_name)

    return ax.valid_http(http_success=HTTPCreated, detail=s.Users_POST_CreatedResponseSchema.description,
                         content={"user": uf.format_user(new_user, new_user_groups)})
예제 #27
0
def get_user_resources_view(request):
    """
    List all resources a user has permissions on.
    """
    inherit_groups_perms = asbool(
        ar.get_query_param(request, ["inherit", "inherited"]))
    resolve_groups_perms = asbool(
        ar.get_query_param(request, ["resolve", "resolved"]))
    filtered_perms = asbool(ar.get_query_param(request,
                                               ["filter", "filtered"]))
    user = ar.get_user_matchdict_checked_or_logged(request)
    db = request.db

    # skip admin-only full listing of resources if filtered view is requested
    is_admin = False
    if not filtered_perms and request.user is not None:
        admin_group = get_constant("MAGPIE_ADMIN_GROUP",
                                   settings_container=request)
        is_admin = admin_group in [
            group.group_name for group in request.user.groups
        ]

    def build_json_user_resource_tree(usr):
        json_res = {}
        perm_type = PermissionType.INHERITED if inherit_groups_perms else PermissionType.DIRECT
        services = ResourceService.all(models.Service, db_session=db)
        # add service-types so they are ordered and listed if no service of that type was defined
        for svc_type in sorted(SERVICE_TYPE_DICT):
            json_res[svc_type] = {}
        for svc in services:
            svc_perms = uu.get_user_service_permissions(
                user=usr,
                service=svc,
                request=request,
                inherit_groups_permissions=inherit_groups_perms,
                resolve_groups_permissions=resolve_groups_perms)
            res_perms_dict = uu.get_user_service_resources_permissions_dict(
                user=usr,
                service=svc,
                request=request,
                inherit_groups_permissions=inherit_groups_perms,
                resolve_groups_permissions=resolve_groups_perms)
            # always allow admin to view full resource tree, unless explicitly requested to be filtered
            # otherwise (non-admin), only add details if there is at least one resource permission (any level)
            if (is_admin and not filtered_perms) or (svc_perms
                                                     or res_perms_dict):
                json_res[svc.type][
                    svc.resource_name] = format_service_resources(
                        svc,
                        db_session=db,
                        service_perms=svc_perms,
                        resources_perms_dict=res_perms_dict,
                        permission_type=perm_type,
                        show_all_children=False,
                        show_private_url=False,
                    )
        return json_res

    usr_res_dict = ax.evaluate_call(
        lambda: build_json_user_resource_tree(user),
        fallback=lambda: db.rollback(),
        http_error=HTTPNotFound,
        msg_on_fail=s.UserResources_GET_NotFoundResponseSchema.description,
        content={
            "user_name": user.user_name,
            "resource_types": [models.Service.resource_type_name]
        })
    return ax.valid_http(
        http_success=HTTPOk,
        content={"resources": usr_res_dict},
        detail=s.UserResources_GET_OkResponseSchema.description)
예제 #28
0
파일: db.py 프로젝트: bird-house/Magpie
def run_database_migration_when_ready(settings, db_session=None):
    # type: (SettingsType, Optional[Session]) -> None
    """
    Runs db migration if requested by config and need from revisions.
    """

    db_ready = False
    if asbool(
            get_constant("MAGPIE_DB_MIGRATION",
                         settings,
                         "magpie.db_migration",
                         default_value=True,
                         raise_missing=False,
                         raise_not_set=False,
                         print_missing=True)):
        attempts = int(
            get_constant("MAGPIE_DB_MIGRATION_ATTEMPTS",
                         settings,
                         "magpie.db_migration_attempts",
                         default_value=5,
                         raise_missing=False,
                         raise_not_set=False,
                         print_missing=True))

        print_log("Running database migration (as required)...")
        attempts = max(
            attempts, 2
        )  # enforce at least 2 attempts, 1 for db creation and one for actual migration
        for i in range(1, attempts + 1):
            try:
                with warnings.catch_warnings():
                    warnings.simplefilter("ignore", category=sa_exc.SAWarning)
                    run_database_migration(db_session)
            except ImportError as e:
                print_log(
                    "Database migration produced [{!r}] (ignored).".format(e),
                    level=logging.WARNING)
                pass
            except Exception as e:
                if i <= attempts:
                    print_log(
                        "Database migration failed [{!r}]. Retrying... ({}/{})"
                        .format(e, i, attempts))
                    time.sleep(2)
                    continue
                else:
                    raise_log("Database migration failed [{!r}]".format(e),
                              exception=RuntimeError)

            db_ready = is_database_ready(db_session)
            if not db_ready:
                print_log("Database not ready. Retrying... ({}/{})".format(
                    i, attempts))
                time.sleep(2)
                continue
            break
    else:
        db_ready = is_database_ready(db_session)
    if not db_ready:
        time.sleep(2)
        raise_log("Database not ready", exception=RuntimeError)
예제 #29
0
def check_or_try_login_user(
        test_item,  # type: AnyMagpieTestType
        username=None,  # type: Optional[Str]
        password=None,  # type: Optional[Str]
        provider=None,  # type: Optional[Str]
        headers=None,  # type: Optional[Dict[Str, Str]]
        use_ui_form_submit=False,  # type: bool
        version=__meta__.__version__,  # type: Str
        expect_errors=False,  # type: bool
):  # type: (...) -> OptionalHeaderCookiesType
    """
    Verifies that the required user is already logged in (or none is if username=None), or tries to login him otherwise.
    Validates that the logged user (if any), matched the one specified with `username`.

    :param test_item: instance of the test application or remote server URL to call
    :param username: name of the user to login or None otherwise
    :param password: password to use for login if the user was not already logged in
    :param provider: provider string to use for login (default: `MAGPIE_DEFAULT_PROVIDER`, ie: magpie's local signin)
    :param headers: headers to include in the test request
    :param use_ui_form_submit: use Magpie UI login 'form' to obtain cookies
        (required for local `WebTest.App` login, ignored by requests using URL)
    :param version: server or local app version to evaluate responses with backward compatibility
    :param expect_errors: indicate if the login is expected to fail, used only if using UI form & `webtest.TestApp`
    :return: headers and cookies of the user session or (None, None)
    :raise: Exception on any login failure as required by the caller's specifications (username/password)
    """
    app_or_url = get_app_or_url(test_item)
    headers = headers or {}
    resp = get_session_user(app_or_url, headers)
    body = get_json_body(resp)

    resp_cookies = None
    auth = body.get("authenticated", False)
    if auth is False and username is None:
        return None, None
    if auth is False and username is not None:
        provider = provider or get_constant("MAGPIE_DEFAULT_PROVIDER")
        data = {
            "user_name": username,
            "password": password,
            "provider_name": provider
        }

        if isinstance(app_or_url, TestApp):
            if use_ui_form_submit:
                base_url = app_or_url.app.registry.settings.get("magpie.url")
                resp = app_or_url.get(url="{}/ui/login".format(base_url))
                form = resp.forms["login_internal"]
                form["user_name"] = username
                form["password"] = password
                form["provider_name"] = provider
                resp = form.submit("submit", expect_errors=expect_errors)
                resp_cookies = app_or_url.cookies  # automatically set by form submit
            else:
                resp = app_or_url.post_json("/signin", data, headers=headers)
                resp_cookies = resp.cookies
        else:
            resp = requests.post("{}/signin".format(app_or_url),
                                 json=data,
                                 headers=headers)
            resp_cookies = resp.cookies

        # response OK (200) if directly from API /signin
        # response Found (302) if redirected UI /login
        if resp.status_code < 400:
            return resp.headers, resp_cookies

    if auth is True:
        if LooseVersion(version) >= LooseVersion("0.6.3"):
            logged_user = body.get("user", {}).get("user_name", "")
        else:
            logged_user = body.get("user_name", "")
        if username != logged_user:
            raise Exception("invalid user")
        if isinstance(app_or_url, TestApp):
            resp_cookies = app_or_url.cookies
        else:
            resp_cookies = resp.cookies

    return resp.headers, resp_cookies
예제 #30
0
def get_phoenix_url():
    hostname = get_constant("HOSTNAME")
    phoenix_port = get_constant("PHOENIX_PORT", raise_not_set=False)
    return "https://{0}{1}".format(
        hostname, ":{}".format(phoenix_port) if phoenix_port else "")