Exemple #1
0
def tools_shell(command=None):
    """
    Launch an (i)python shell in the YunoHost context.

    This is entirely aim for development.
    """

    from yunohost.utils.ldap import _get_ldap_interface
    ldap = _get_ldap_interface()

    if command:
        exec(command)
        return

    logger.warn(
        "The \033[1;34mldap\033[0m interface is available in this context")
    try:
        from IPython import embed
        embed()
    except ImportError:
        logger.warn(
            "You don't have IPython installed, consider installing it as it is way better than the standard shell."
        )
        logger.warn("Falling back on the standard shell.")

        import readline  # will allow Up/Down/History in the console
        readline  # to please pyflakes
        import code
        vars = globals().copy()
        vars.update(locals())
        shell = code.InteractiveConsole(vars)
        shell.interact()
Exemple #2
0
def user_group_info(groupname):
    """
    Get user informations

    Keyword argument:
        groupname -- Groupname to get informations

    """

    from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
    ldap = _get_ldap_interface()

    # Fetch info for this group
    result = ldap.search('ou=groups,dc=yunohost,dc=org', "cn=" + groupname,
                         ["cn", "member", "permission"])

    if not result:
        raise YunohostError('group_unknown', group=groupname)

    infos = result[0]

    # Format data

    return {
        'members':
        [_ldap_path_extract(p, "uid") for p in infos.get("member", [])],
        'permissions':
        [_ldap_path_extract(p, "cn") for p in infos.get("permission", [])]
    }
Exemple #3
0
def _get_user_for_ssh(username, attrs=None):
    def ssh_root_login_status():
        # XXX temporary placed here for when the ssh_root commands are integrated
        # extracted from https://github.com/YunoHost/yunohost/pull/345
        # XXX should we support all the options?
        # this is the content of "man sshd_config"
        # PermitRootLogin
        #     Specifies whether root can log in using ssh(1).  The argument must be
        #     “yes”, “without-password”, “forced-commands-only”, or “no”.  The
        #     default is “yes”.
        sshd_config_content = read_file(SSHD_CONFIG_PATH)

        if re.search(
            "^ *PermitRootLogin +(no|forced-commands-only) *$",
            sshd_config_content,
            re.MULTILINE,
        ):
            return {"PermitRootLogin": False}

        return {"PermitRootLogin": True}

    if username == "root":
        root_unix = pwd.getpwnam("root")
        return {
            "username": "******",
            "fullname": "",
            "mail": "",
            "ssh_allowed": ssh_root_login_status()["PermitRootLogin"],
            "shell": root_unix.pw_shell,
            "home_path": root_unix.pw_dir,
        }

    if username == "admin":
        admin_unix = pwd.getpwnam("admin")
        return {
            "username": "******",
            "fullname": "",
            "mail": "",
            "ssh_allowed": admin_unix.pw_shell.strip() != "/bin/false",
            "shell": admin_unix.pw_shell,
            "home_path": admin_unix.pw_dir,
        }

    # TODO escape input using https://www.python-ldap.org/doc/html/ldap-filter.html
    from yunohost.utils.ldap import _get_ldap_interface

    ldap = _get_ldap_interface()
    user = ldap.search(
        "ou=users,dc=yunohost,dc=org",
        "(&(objectclass=person)(uid=%s))" % username,
        attrs,
    )

    assert len(user) in (0, 1)

    if not user:
        return None

    return user[0]
    def add_new_ldap_attributes(self):

        from yunohost.utils.ldap import _get_ldap_interface
        from yunohost.regenconf import regen_conf, BACKUP_CONF_DIR

        # Check if the migration can be processed
        ldap_regen_conf_status = regen_conf(names=["slapd"], dry_run=True)
        # By this we check if the have been customized
        if ldap_regen_conf_status and ldap_regen_conf_status["slapd"][
                "pending"]:
            logger.warning(
                m18n.n(
                    "migration_0019_slapd_config_will_be_overwritten",
                    conf_backup_folder=BACKUP_CONF_DIR,
                ))

        # Update LDAP schema restart slapd
        logger.info(m18n.n("migration_0011_update_LDAP_schema"))
        regen_conf(names=["slapd"], force=True)

        logger.info(m18n.n("migration_0019_add_new_attributes_in_ldap"))
        ldap = _get_ldap_interface()
        permission_list = user_permission_list(full=True)["permissions"]

        for permission in permission_list:
            system_perms = {
                "mail": "E-mail",
                "xmpp": "XMPP",
                "ssh": "SSH",
                "sftp": "STFP",
            }
            if permission.split(".")[0] in system_perms:
                update = {
                    "authHeader": ["FALSE"],
                    "label": [system_perms[permission.split(".")[0]]],
                    "showTile": ["FALSE"],
                    "isProtected": ["TRUE"],
                }
            else:
                app, subperm_name = permission.split(".")
                if permission.endswith(".main"):
                    update = {
                        "authHeader": ["TRUE"],
                        "label": [
                            app
                        ],  # Note that this is later re-changed during the call to migrate_legacy_permission_settings() if a 'label' setting exists
                        "showTile": ["TRUE"],
                        "isProtected": ["FALSE"],
                    }
                else:
                    update = {
                        "authHeader": ["TRUE"],
                        "label": [subperm_name.title()],
                        "showTile": ["FALSE"],
                        "isProtected": ["TRUE"],
                    }

            ldap.update("cn=%s,ou=permission" % permission, update)
Exemple #5
0
def permission_sync_to_user():
    """
    Sychronise the inheritPermission attribut in the permission object from the
    user<->group link and the group<->permission link
    """
    import os
    from yunohost.app import app_ssowatconf
    from yunohost.user import user_group_list
    from yunohost.utils.ldap import _get_ldap_interface

    ldap = _get_ldap_interface()

    groups = user_group_list(full=True)["groups"]
    permissions = user_permission_list(full=True)["permissions"]

    for permission_name, permission_infos in permissions.items():

        # These are the users currently allowed because there's an 'inheritPermission' object corresponding to it
        currently_allowed_users = set(permission_infos["corresponding_users"])

        # These are the users that should be allowed because they are member of a group that is allowed for this permission ...
        should_be_allowed_users = set([
            user for group in permission_infos["allowed"]
            for user in groups[group]["members"]
        ])

        # Note that a LDAP operation with the same value that is in LDAP crash SLAP.
        # So we need to check before each ldap operation that we really change something in LDAP
        if currently_allowed_users == should_be_allowed_users:
            # We're all good, this permission is already correctly synchronized !
            continue

        new_inherited_perms = {
            "inheritPermission": [
                "uid=%s,ou=users,dc=yunohost,dc=org" % u
                for u in should_be_allowed_users
            ],
            "memberUid":
            should_be_allowed_users,
        }

        # Commit the change with the new inherited stuff
        try:
            ldap.update("cn=%s,ou=permission" % permission_name,
                        new_inherited_perms)
        except Exception as e:
            raise YunohostError("permission_update_failed",
                                permission=permission_name,
                                error=e)

    logger.debug("The permission database has been resynchronized")

    app_ssowatconf()

    # Reload unscd, otherwise the group ain't propagated to the LDAP database
    os.system("nscd --invalidate=passwd")
    os.system("nscd --invalidate=group")
    def run(self, *args):

        from yunohost.utils.ldap import _get_ldap_interface

        ldap = _get_ldap_interface()

        existing_perms_raw = ldap.search(
            "ou=permission,dc=yunohost,dc=org", "(objectclass=permissionYnh)", ["cn"]
        )
        existing_perms = [perm["cn"][0] for perm in existing_perms_raw]

        # Add SSH and SFTP permissions
        ldap_map = read_yaml(
            "/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml"
        )

        if "sftp.main" not in existing_perms:
            ldap.add(
                "cn=sftp.main,ou=permission",
                ldap_map["depends_children"]["cn=sftp.main,ou=permission"],
            )

        if "ssh.main" not in existing_perms:
            ldap.add(
                "cn=ssh.main,ou=permission",
                ldap_map["depends_children"]["cn=ssh.main,ou=permission"],
            )

            # Add a bash terminal to each users
            users = ldap.search(
                "ou=users,dc=yunohost,dc=org",
                filter="(loginShell=*)",
                attrs=["dn", "uid", "loginShell"],
            )
            for user in users:
                if user["loginShell"][0] == "/bin/false":
                    dn = user["dn"][0].replace(",dc=yunohost,dc=org", "")
                    ldap.update(dn, {"loginShell": ["/bin/bash"]})
                else:
                    user_permission_update(
                        "ssh.main", add=user["uid"][0], sync_perm=False
                    )

            permission_sync_to_user()

            # Somehow this is needed otherwise the PAM thing doesn't forget about the
            # old loginShell value ?
            subprocess.call(["nscd", "-i", "passwd"])

        if (
            "/etc/ssh/sshd_config" in manually_modified_files()
            and os.system(
                "grep -q '^ *AllowGroups\\|^ *AllowUsers' /etc/ssh/sshd_config"
            )
            != 0
        ):
            logger.error(m18n.n("diagnosis_sshd_config_insecure"))
Exemple #7
0
def tools_adminpw(new_password, check_strength=True):
    """
    Change admin password

    Keyword argument:
        new_password

    """
    from yunohost.user import _hash_user_password
    from yunohost.utils.password import assert_password_is_strong_enough
    import spwd

    if check_strength:
        assert_password_is_strong_enough("admin", new_password)

    # UNIX seems to not like password longer than 127 chars ...
    # e.g. SSH login gets broken (or even 'su admin' when entering the password)
    if len(new_password) >= 127:
        raise YunohostValidationError("admin_password_too_long")

    new_hash = _hash_user_password(new_password)

    from yunohost.utils.ldap import _get_ldap_interface

    ldap = _get_ldap_interface()

    try:
        ldap.update(
            "cn=admin",
            {
                "userPassword": [new_hash],
            },
        )
    except Exception:
        logger.error("unable to change admin password")
        raise YunohostError("admin_password_change_failed")
    else:
        # Write as root password
        try:
            hash_root = spwd.getspnam("root").sp_pwd

            with open("/etc/shadow", "r") as before_file:
                before = before_file.read()

            with open("/etc/shadow", "w") as after_file:
                after_file.write(
                    before.replace("root:" + hash_root,
                                   "root:" + new_hash.replace("{CRYPT}", "")))
        # An IOError may be thrown if for some reason we can't read/write /etc/passwd
        # A KeyError could also be thrown if 'root' is not in /etc/passwd in the first place (for example because no password defined ?)
        # (c.f. the line about getspnam)
        except (IOError, KeyError):
            logger.warning(m18n.n("root_password_desynchronized"))
            return

        logger.info(m18n.n("root_password_replaced_by_admin_password"))
        logger.success(m18n.n("admin_password_changed"))
Exemple #8
0
    def migrate_LDAP_db():

        logger.info(m18n.n("migration_0011_update_LDAP_database"))

        from yunohost.utils.ldap import _get_ldap_interface
        ldap = _get_ldap_interface()

        ldap_map = read_yaml(
            '/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml')

        try:
            SetupGroupPermissions.remove_if_exists("ou=permission")
            SetupGroupPermissions.remove_if_exists('ou=groups')

            attr_dict = ldap_map['parents']['ou=permission']
            ldap.add('ou=permission', attr_dict)

            attr_dict = ldap_map['parents']['ou=groups']
            ldap.add('ou=groups', attr_dict)

            attr_dict = ldap_map['children']['cn=all_users,ou=groups']
            ldap.add('cn=all_users,ou=groups', attr_dict)

            attr_dict = ldap_map['children']['cn=visitors,ou=groups']
            ldap.add('cn=visitors,ou=groups', attr_dict)

            for rdn, attr_dict in ldap_map['depends_children'].items():
                ldap.add(rdn, attr_dict)
        except Exception as e:
            raise YunohostError("migration_0011_LDAP_update_failed", error=e)

        logger.info(m18n.n("migration_0011_create_group"))

        # Create a group for each yunohost user
        user_list = ldap.search(
            'ou=users,dc=yunohost,dc=org',
            '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))',
            ['uid', 'uidNumber'])
        for user_info in user_list:
            username = user_info['uid'][0]
            ldap.update(
                'uid=%s,ou=users' % username, {
                    'objectClass': [
                        'mailAccount', 'inetOrgPerson', 'posixAccount',
                        'userPermissionYnh'
                    ]
                })
            user_group_create(username,
                              gid=user_info['uidNumber'][0],
                              primary_group=True,
                              sync_perm=False)
            user_group_update(groupname='all_users',
                              add=username,
                              force=True,
                              sync_perm=False)
Exemple #9
0
def user_group_list(short=False, full=False, include_primary_groups=True):
    """
    List users

    Keyword argument:
        short -- Only list the name of the groups without any additional info
        full -- List all the info available for each groups
        include_primary_groups -- Include groups corresponding to users (which should always only contains this user)
                                  This option is set to false by default in the action map because we don't want to have
                                  these displayed when the user runs `yunohost user group list`, but internally we do want
                                  to list them when called from other functions
    """

    # Fetch relevant informations

    from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract

    ldap = _get_ldap_interface()
    groups_infos = ldap.search(
        "ou=groups,dc=yunohost,dc=org",
        "(objectclass=groupOfNamesYnh)",
        ["cn", "member", "permission"],
    )

    # Parse / organize information to be outputed

    users = user_list()["users"]
    groups = {}
    for infos in groups_infos:

        name = infos["cn"][0]

        if not include_primary_groups and name in users:
            continue

        groups[name] = {}

        groups[name]["members"] = [
            _ldap_path_extract(p, "uid") for p in infos.get("member", [])
        ]

        if full:
            groups[name]["permissions"] = [
                _ldap_path_extract(p, "cn")
                for p in infos.get("permission", [])
            ]

    if short:
        groups = list(groups.keys())

    return {"groups": groups}
Exemple #10
0
def user_delete(operation_logger, username, purge=False):
    """
    Delete user

    Keyword argument:
        username -- Username to delete
        purge

    """
    from yunohost.hook import hook_callback
    from yunohost.utils.ldap import _get_ldap_interface

    if username not in user_list()["users"]:
        raise YunohostError('user_unknown', user=username)

    operation_logger.start()

    user_group_update("all_users",
                      remove=username,
                      force=True,
                      sync_perm=False)
    for group, infos in user_group_list()["groups"].items():
        if group == "all_users":
            continue
        # If the user is in this group (and it's not the primary group),
        # remove the member from the group
        if username != group and username in infos["members"]:
            user_group_update(group, remove=username, sync_perm=False)

    # Delete primary group if it exists (why wouldnt it exists ?  because some
    # epic bug happened somewhere else and only a partial removal was
    # performed...)
    if username in user_group_list()['groups'].keys():
        user_group_delete(username, force=True, sync_perm=True)

    ldap = _get_ldap_interface()
    try:
        ldap.remove('uid=%s,ou=users' % username)
    except Exception as e:
        raise YunohostError('user_deletion_failed', user=username, error=e)

    # Invalidate passwd to take user deletion into account
    subprocess.call(['nscd', '-i', 'passwd'])

    if purge:
        subprocess.call(['rm', '-rf', '/home/{0}'.format(username)])
        subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)])

    hook_callback('post_user_delete', args=[username, purge])

    logger.success(m18n.n('user_deleted'))
Exemple #11
0
def user_list(fields=None):

    from yunohost.utils.ldap import _get_ldap_interface

    user_attrs = {
        "uid": "username",
        "cn": "fullname",
        "mail": "mail",
        "maildrop": "mail-forward",
        "loginShell": "shell",
        "homeDirectory": "home_path",
        "mailuserquota": "mailbox-quota",
    }

    attrs = ["uid"]
    users = {}

    if fields:
        keys = user_attrs.keys()
        for attr in fields:
            if attr in keys:
                attrs.append(attr)
            else:
                raise YunohostError("field_invalid", attr)
    else:
        attrs = ["uid", "cn", "mail", "mailuserquota", "loginShell"]

    ldap = _get_ldap_interface()
    result = ldap.search(
        "ou=users,dc=yunohost,dc=org",
        "(&(objectclass=person)(!(uid=root))(!(uid=nobody)))",
        attrs,
    )

    for user in result:
        entry = {}
        for attr, values in user.items():
            if values:
                if attr == "loginShell":
                    if values[0].strip() == "/bin/false":
                        entry["ssh_allowed"] = False
                    else:
                        entry["ssh_allowed"] = True

                entry[user_attrs[attr]] = values[0]

        uid = entry[user_attrs["uid"]]
        users[uid] = entry

    return {"users": users}
Exemple #12
0
def user_list(fields=None):

    from yunohost.utils.ldap import _get_ldap_interface

    user_attrs = {
        'uid': 'username',
        'cn': 'fullname',
        'mail': 'mail',
        'maildrop': 'mail-forward',
        'loginShell': 'shell',
        'homeDirectory': 'home_path',
        'mailuserquota': 'mailbox-quota'
    }

    attrs = ['uid']
    users = {}

    if fields:
        keys = user_attrs.keys()
        for attr in fields:
            if attr in keys:
                attrs.append(attr)
            else:
                raise YunohostError('field_invalid', attr)
    else:
        attrs = ['uid', 'cn', 'mail', 'mailuserquota', 'loginShell']

    ldap = _get_ldap_interface()
    result = ldap.search(
        'ou=users,dc=yunohost,dc=org',
        '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))', attrs)

    for user in result:
        entry = {}
        for attr, values in user.items():
            if values:
                if attr == "loginShell":
                    if values[0].strip() == "/bin/false":
                        entry["ssh_allowed"] = False
                    else:
                        entry["ssh_allowed"] = True

                entry[user_attrs[attr]] = values[0]

        uid = entry[user_attrs['uid']]
        users[uid] = entry

    return {'users': users}
Exemple #13
0
def user_ssh_disallow(username):
    """
    Disallow YunoHost user connect as ssh.

    Keyword argument:
        username -- User username
    """
    # TODO it would be good to support different kind of shells

    if not _get_user_for_ssh(username):
        raise YunohostError('user_unknown', user=username)

    from yunohost.utils.ldap import _get_ldap_interface
    ldap = _get_ldap_interface()
    ldap.update('uid=%s,ou=users' % username, {'loginShell': ['/bin/false']})

    # Somehow this is needed otherwise the PAM thing doesn't forget about the
    # old loginShell value ?
    subprocess.call(['nscd', '-i', 'passwd'])
Exemple #14
0
def user_group_delete(operation_logger,
                      groupname,
                      force=False,
                      sync_perm=True):
    """
    Delete user

    Keyword argument:
        groupname -- Groupname to delete

    """
    from yunohost.permission import permission_sync_to_user
    from yunohost.utils.ldap import _get_ldap_interface

    existing_groups = list(user_group_list()["groups"].keys())
    if groupname not in existing_groups:
        raise YunohostValidationError("group_unknown", group=groupname)

    # Refuse to delete primary groups of a user (e.g. group 'sam' related to user 'sam')
    # without the force option...
    #
    # We also can't delete "all_users" because that's a special group...
    existing_users = list(user_list()["users"].keys())
    undeletable_groups = existing_users + ["all_users", "visitors"]
    if groupname in undeletable_groups and not force:
        raise YunohostValidationError("group_cannot_be_deleted",
                                      group=groupname)

    operation_logger.start()
    ldap = _get_ldap_interface()
    try:
        ldap.remove("cn=%s,ou=groups" % groupname)
    except Exception as e:
        raise YunohostError("group_deletion_failed", group=groupname, error=e)

    if sync_perm:
        permission_sync_to_user()

    if groupname not in existing_users:
        logger.success(m18n.n("group_deleted", group=groupname))
    else:
        logger.debug(m18n.n("group_deleted", group=groupname))
Exemple #15
0
def user_ssh_disallow(username):
    """
    Disallow YunoHost user connect as ssh.

    Keyword argument:
        username -- User username
    """
    # TODO it would be good to support different kind of shells

    if not _get_user_for_ssh(username):
        raise YunohostValidationError("user_unknown", user=username)

    from yunohost.utils.ldap import _get_ldap_interface

    ldap = _get_ldap_interface()
    ldap.update("uid=%s,ou=users" % username, {"loginShell": ["/bin/false"]})

    # Somehow this is needed otherwise the PAM thing doesn't forget about the
    # old loginShell value ?
    subprocess.call(["nscd", "-i", "passwd"])
Exemple #16
0
def permission_delete(operation_logger,
                      permission,
                      force=False,
                      sync_perm=True):
    """
    Delete a permission

    Keyword argument:
        permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
    """

    # By default, manipulate main permission
    if "." not in permission:
        permission = permission + ".main"

    if permission.endswith(".main") and not force:
        raise YunohostValidationError("permission_cannot_remove_main")

    from yunohost.utils.ldap import _get_ldap_interface

    ldap = _get_ldap_interface()

    # Make sure this permission exists

    _ = user_permission_info(permission)

    # Actually delete the permission

    operation_logger.related_to.append(("app", permission.split(".")[0]))
    operation_logger.start()

    try:
        ldap.remove("cn=%s,ou=permission" % permission)
    except Exception as e:
        raise YunohostError("permission_deletion_failed",
                            permission=permission,
                            error=e)

    if sync_perm:
        permission_sync_to_user()
    logger.debug(m18n.n("permission_deleted", permission=permission))
Exemple #17
0
def domain_list(exclude_subdomains=False):
    """
    List domains

    Keyword argument:
        exclude_subdomains -- Filter out domains that are subdomains of other declared domains

    """
    from yunohost.utils.ldap import _get_ldap_interface

    ldap = _get_ldap_interface()
    result = [
        entry["virtualdomain"][0]
        for entry in ldap.search("ou=domains,dc=yunohost,dc=org",
                                 "virtualdomain=*", ["virtualdomain"])
    ]

    result_list = []
    for domain in result:
        if exclude_subdomains:
            parent_domain = domain.split(".", 1)[1]
            if parent_domain in result:
                continue

        result_list.append(domain)

    def cmp_domain(domain):
        # Keep the main part of the domain and the extension together
        # eg: this.is.an.example.com -> ['example.com', 'an', 'is', 'this']
        domain = domain.split(".")
        domain[-1] = domain[-2] + domain.pop()
        domain = list(reversed(domain))
        return domain

    result_list = sorted(result_list, key=cmp_domain)

    return {"domains": result_list, "main": _get_maindomain()}
Exemple #18
0
    def remove_if_exists(target):

        from yunohost.utils.ldap import _get_ldap_interface
        ldap = _get_ldap_interface()

        try:
            objects = ldap.search(target + ",dc=yunohost,dc=org")
        # ldap search will raise an exception if no corresponding object is found >.> ...
        except Exception:
            logger.debug("%s does not exist, no need to delete it" % target)
            return

        objects.reverse()
        for o in objects:
            for dn in o["dn"]:
                dn = dn.replace(",dc=yunohost,dc=org", "")
                logger.debug("Deleting old object %s ..." % dn)
                try:
                    ldap.remove(dn)
                except Exception as e:
                    raise YunohostError(
                        "migration_0011_failed_to_remove_stale_object",
                        dn=dn,
                        error=e)
Exemple #19
0
def permission_url(
    operation_logger,
    permission,
    url=None,
    add_url=None,
    remove_url=None,
    auth_header=None,
    clear_urls=False,
    sync_perm=True,
):
    """
    Update urls related to a permission for a specific application

    Keyword argument:
        permission  -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
        url         -- (optional) URL for which access will be allowed/forbidden.
        add_url     -- (optional) List of additional url to add for which access will be allowed/forbidden
        remove_url  -- (optional) List of additional url to remove for which access will be allowed/forbidden
        auth_header -- (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application
        clear_urls  -- (optional) Clean all urls (url and additional_urls)
    """
    from yunohost.app import app_setting
    from yunohost.utils.ldap import _get_ldap_interface

    ldap = _get_ldap_interface()

    # By default, manipulate main permission
    if "." not in permission:
        permission = permission + ".main"

    app = permission.split(".")[0]

    if url or add_url:
        domain = app_setting(app, "domain")
        path = app_setting(app, "path")
        if domain is None or path is None:
            raise YunohostError("unknown_main_domain_path", app=app)
        else:
            app_main_path = domain + path

    # Fetch existing permission

    existing_permission = user_permission_info(permission)

    show_tile = existing_permission["show_tile"]

    if url is None:
        url = existing_permission["url"]
    else:
        url = _validate_and_sanitize_permission_url(url, app_main_path, app)

        if url.startswith("re:") and existing_permission["show_tile"]:
            logger.warning(
                m18n.n("regex_incompatible_with_tile",
                       regex=url,
                       permission=permission))
            show_tile = False

    current_additional_urls = existing_permission["additional_urls"]
    new_additional_urls = copy.copy(current_additional_urls)

    if add_url:
        for ur in add_url:
            if ur in current_additional_urls:
                logger.warning(
                    m18n.n("additional_urls_already_added",
                           permission=permission,
                           url=ur))
            else:
                ur = _validate_and_sanitize_permission_url(
                    ur, app_main_path, app)
                new_additional_urls += [ur]

    if remove_url:
        for ur in remove_url:
            if ur not in current_additional_urls:
                logger.warning(
                    m18n.n("additional_urls_already_removed",
                           permission=permission,
                           url=ur))

        new_additional_urls = [
            u for u in new_additional_urls if u not in remove_url
        ]

    if auth_header is None:
        auth_header = existing_permission["auth_header"]

    if clear_urls:
        url = None
        new_additional_urls = []
        show_tile = False

    # Guarantee uniqueness of all values, which would otherwise make ldap.update angry.
    new_additional_urls = set(new_additional_urls)

    # Actually commit the change

    operation_logger.related_to.append(("app", permission.split(".")[0]))
    operation_logger.start()

    try:
        ldap.update(
            "cn=%s,ou=permission" % permission,
            {
                "URL": [url] if url is not None else [],
                "additionalUrls": new_additional_urls,
                "authHeader": [str(auth_header).upper()],
                "showTile": [str(show_tile).upper()],
            },
        )
    except Exception as e:
        raise YunohostError("permission_update_failed",
                            permission=permission,
                            error=e)

    if sync_perm:
        permission_sync_to_user()

    logger.debug(m18n.n("permission_updated", permission=permission))
    return user_permission_info(permission)
Exemple #20
0
def user_permission_list(short=False,
                         full=False,
                         ignore_system_perms=False,
                         absolute_urls=False):
    """
    List permissions and corresponding accesses
    """

    # Fetch relevant informations
    from yunohost.app import app_setting, _installed_apps
    from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract

    ldap = _get_ldap_interface()
    permissions_infos = ldap.search(
        "ou=permission,dc=yunohost,dc=org",
        "(objectclass=permissionYnh)",
        [
            "cn",
            "groupPermission",
            "inheritPermission",
            "URL",
            "additionalUrls",
            "authHeader",
            "label",
            "showTile",
            "isProtected",
        ],
    )

    # Parse / organize information to be outputed
    apps = sorted(_installed_apps())
    apps_base_path = {
        app: app_setting(app, "domain") + app_setting(app, "path")
        for app in apps
        if app_setting(app, "domain") and app_setting(app, "path")
    }

    permissions = {}
    for infos in permissions_infos:

        name = infos["cn"][0]
        if ignore_system_perms and name.split(".")[0] in SYSTEM_PERMS:
            continue

        app = name.split(".")[0]

        perm = {}
        perm["allowed"] = [
            _ldap_path_extract(p, "cn")
            for p in infos.get("groupPermission", [])
        ]

        if full:
            perm["corresponding_users"] = [
                _ldap_path_extract(p, "uid")
                for p in infos.get("inheritPermission", [])
            ]
            perm["auth_header"] = infos.get("authHeader", [False])[0] == "TRUE"
            perm["label"] = infos.get("label", [None])[0]
            perm["show_tile"] = infos.get("showTile", [False])[0] == "TRUE"
            perm["protected"] = infos.get("isProtected", [False])[0] == "TRUE"
            perm["url"] = infos.get("URL", [None])[0]
            perm["additional_urls"] = infos.get("additionalUrls", [])

            if absolute_urls:
                app_base_path = (
                    apps_base_path[app] if app in apps_base_path else ""
                )  # Meh in some situation where the app is currently installed/removed, this function may be called and we still need to act as if the corresponding permission indeed exists ... dunno if that's really the right way to proceed but okay.
                perm["url"] = _get_absolute_url(perm["url"], app_base_path)
                perm["additional_urls"] = [
                    _get_absolute_url(url, app_base_path)
                    for url in perm["additional_urls"]
                ]

        permissions[name] = perm

    # Make sure labels for sub-permissions are the form " Applabel (Sublabel) "
    if full:
        subpermissions = {
            k: v
            for k, v in permissions.items() if not k.endswith(".main")
        }
        for name, infos in subpermissions.items():
            main_perm_name = name.split(".")[0] + ".main"
            if main_perm_name not in permissions:
                logger.debug(
                    "Uhoh, unknown permission %s ? (Maybe we're in the process or deleting the perm for this app...)"
                    % main_perm_name)
                continue
            main_perm_label = permissions[main_perm_name]["label"]
            infos["sublabel"] = infos["label"]
            infos["label"] = "%s (%s)" % (main_perm_label, infos["label"])

    if short:
        permissions = list(permissions.keys())

    return {"permissions": permissions}
Exemple #21
0
def permission_create(
    operation_logger,
    permission,
    allowed=None,
    url=None,
    additional_urls=None,
    auth_header=True,
    label=None,
    show_tile=False,
    protected=False,
    sync_perm=True,
):
    """
    Create a new permission for a specific application

    Keyword argument:
        permission      -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
        allowed         -- (optional) List of group/user to allow for the permission
        url             -- (optional) URL for which access will be allowed/forbidden
        additional_urls -- (optional) List of additional URL for which access will be allowed/forbidden
        auth_header     -- (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application
        label           -- (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. Default is "permission name"
        show_tile       -- (optional) Define if a tile will be shown in the SSO
        protected       -- (optional) Define if the permission can be added/removed to the visitor group

    If provided, 'url' is assumed to be relative to the app domain/path if they
    start with '/'.  For example:
       /                             -> domain.tld/app
       /admin                        -> domain.tld/app/admin
       domain.tld/app/api            -> domain.tld/app/api

    'url' can be later treated as a regex if it starts with "re:".
    For example:
       re:/api/[A-Z]*$               -> domain.tld/app/api/[A-Z]*$
       re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$
    """

    from yunohost.utils.ldap import _get_ldap_interface
    from yunohost.user import user_group_list

    ldap = _get_ldap_interface()

    # By default, manipulate main permission
    if "." not in permission:
        permission = permission + ".main"

    # Validate uniqueness of permission in LDAP
    if ldap.get_conflict({"cn": permission},
                         base_dn="ou=permission,dc=yunohost,dc=org"):
        raise YunohostValidationError("permission_already_exist",
                                      permission=permission)

    # Get random GID
    all_gid = {x.gr_gid for x in grp.getgrall()}

    uid_guid_found = False
    while not uid_guid_found:
        gid = str(random.randint(200, 99999))
        uid_guid_found = gid not in all_gid

    app, subperm = permission.split(".")

    attr_dict = {
        "objectClass": ["top", "permissionYnh", "posixGroup"],
        "cn":
        str(permission),
        "gidNumber":
        gid,
        "authHeader": ["TRUE"],
        "label": [
            str(label) if label else
            (subperm if subperm != "main" else app.title())
        ],
        "showTile": [
            "FALSE"
        ],  # Dummy value, it will be fixed when we call '_update_ldap_group_permission'
        "isProtected": [
            "FALSE"
        ],  # Dummy value, it will be fixed when we call '_update_ldap_group_permission'
    }

    if allowed is not None:
        if not isinstance(allowed, list):
            allowed = [allowed]

    # Validate that the groups to add actually exist
    all_existing_groups = user_group_list()["groups"].keys()
    for group in allowed or []:
        if group not in all_existing_groups:
            raise YunohostValidationError("group_unknown", group=group)

    operation_logger.related_to.append(("app", permission.split(".")[0]))
    operation_logger.start()

    try:
        ldap.add("cn=%s,ou=permission" % permission, attr_dict)
    except Exception as e:
        raise YunohostError("permission_creation_failed",
                            permission=permission,
                            error=e)

    permission_url(
        permission,
        url=url,
        add_url=additional_urls,
        auth_header=auth_header,
        sync_perm=False,
    )

    new_permission = _update_ldap_group_permission(
        permission=permission,
        allowed=allowed,
        label=label,
        show_tile=show_tile,
        protected=protected,
        sync_perm=sync_perm,
    )

    logger.debug(m18n.n("permission_created", permission=permission))
    return new_permission
Exemple #22
0
def user_create(operation_logger,
                username,
                firstname,
                lastname,
                domain,
                password,
                mailbox_quota="0",
                mail=None):

    from yunohost.domain import domain_list, _get_maindomain
    from yunohost.hook import hook_callback
    from yunohost.utils.password import assert_password_is_strong_enough
    from yunohost.utils.ldap import _get_ldap_interface

    # Ensure sufficiently complex password
    assert_password_is_strong_enough("user", password)

    if mail is not None:
        logger.warning(
            "Packagers ! Using --mail in 'yunohost user create' is deprecated ... please use --domain instead."
        )
        domain = mail.split("@")[-1]

    # Validate domain used for email address/xmpp account
    if domain is None:
        if msettings.get('interface') == 'api':
            raise YunohostError('Invalide usage, specify domain argument')
        else:
            # On affiche les differents domaines possibles
            msignals.display(m18n.n('domains_available'))
            for domain in domain_list()['domains']:
                msignals.display("- {}".format(domain))

            maindomain = _get_maindomain()
            domain = msignals.prompt(
                m18n.n('ask_user_domain') + ' (default: %s)' % maindomain)
            if not domain:
                domain = maindomain

    # Check that the domain exists
    if domain not in domain_list()['domains']:
        raise YunohostError('domain_name_unknown', domain=domain)

    mail = username + '@' + domain
    ldap = _get_ldap_interface()

    if username in user_list()["users"]:
        raise YunohostError("user_already_exists", user=username)

    # Validate uniqueness of username and mail in LDAP
    try:
        ldap.validate_uniqueness({
            'uid': username,
            'mail': mail,
            'cn': username
        })
    except Exception as e:
        raise YunohostError('user_creation_failed', user=username, error=e)

    # Validate uniqueness of username in system users
    all_existing_usernames = {x.pw_name for x in pwd.getpwall()}
    if username in all_existing_usernames:
        raise YunohostError('system_username_exists')

    main_domain = _get_maindomain()
    aliases = [
        'root@' + main_domain,
        'admin@' + main_domain,
        'webmaster@' + main_domain,
        'postmaster@' + main_domain,
        'abuse@' + main_domain,
    ]

    if mail in aliases:
        raise YunohostError('mail_unavailable')

    operation_logger.start()

    # Get random UID/GID
    all_uid = {str(x.pw_uid) for x in pwd.getpwall()}
    all_gid = {str(x.gr_gid) for x in grp.getgrall()}

    uid_guid_found = False
    while not uid_guid_found:
        # LXC uid number is limited to 65536 by default
        uid = str(random.randint(200, 65000))
        uid_guid_found = uid not in all_uid and uid not in all_gid

    # Adapt values for LDAP
    fullname = '%s %s' % (firstname, lastname)

    attr_dict = {
        'objectClass':
        ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh'],
        'givenName': [firstname],
        'sn': [lastname],
        'displayName': [fullname],
        'cn': [fullname],
        'uid': [username],
        'mail':
        mail,  # NOTE: this one seems to be already a list
        'maildrop': [username],
        'mailuserquota': [mailbox_quota],
        'userPassword': [_hash_user_password(password)],
        'gidNumber': [uid],
        'uidNumber': [uid],
        'homeDirectory': ['/home/' + username],
        'loginShell': ['/bin/false']
    }

    # If it is the first user, add some aliases
    if not ldap.search(base='ou=users,dc=yunohost,dc=org', filter='uid=*'):
        attr_dict['mail'] = [attr_dict['mail']] + aliases

    try:
        ldap.add('uid=%s,ou=users' % username, attr_dict)
    except Exception as e:
        raise YunohostError('user_creation_failed', user=username, error=e)

    # Invalidate passwd and group to take user and group creation into account
    subprocess.call(['nscd', '-i', 'passwd'])
    subprocess.call(['nscd', '-i', 'group'])

    try:
        # Attempt to create user home folder
        subprocess.check_call(["mkhomedir_helper", username])
    except subprocess.CalledProcessError:
        if not os.path.isdir('/home/{0}'.format(username)):
            logger.warning(m18n.n('user_home_creation_failed'), exc_info=1)

    # Create group for user and add to group 'all_users'
    user_group_create(groupname=username,
                      gid=uid,
                      primary_group=True,
                      sync_perm=False)
    user_group_update(groupname='all_users',
                      add=username,
                      force=True,
                      sync_perm=True)

    # Trigger post_user_create hooks
    env_dict = {
        "YNH_USER_USERNAME": username,
        "YNH_USER_MAIL": mail,
        "YNH_USER_PASSWORD": password,
        "YNH_USER_FIRSTNAME": firstname,
        "YNH_USER_LASTNAME": lastname
    }

    hook_callback('post_user_create', args=[username, mail], env=env_dict)

    # TODO: Send a welcome mail to user
    logger.success(m18n.n('user_created'))

    return {'fullname': fullname, 'username': username, 'mail': mail}
Exemple #23
0
def tools_ldapinit():
    """
    YunoHost LDAP initialization
    """

    with open("/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml"
              ) as f:
        ldap_map = yaml.load(f)

    from yunohost.utils.ldap import _get_ldap_interface

    ldap = _get_ldap_interface()

    for rdn, attr_dict in ldap_map["parents"].items():
        try:
            ldap.add(rdn, attr_dict)
        except Exception as e:
            logger.warn(
                "Error when trying to inject '%s' -> '%s' into ldap: %s" %
                (rdn, attr_dict, e))

    for rdn, attr_dict in ldap_map["children"].items():
        try:
            ldap.add(rdn, attr_dict)
        except Exception as e:
            logger.warn(
                "Error when trying to inject '%s' -> '%s' into ldap: %s" %
                (rdn, attr_dict, e))

    for rdn, attr_dict in ldap_map["depends_children"].items():
        try:
            ldap.add(rdn, attr_dict)
        except Exception as e:
            logger.warn(
                "Error when trying to inject '%s' -> '%s' into ldap: %s" %
                (rdn, attr_dict, e))

    admin_dict = {
        "cn": ["admin"],
        "uid": ["admin"],
        "description": ["LDAP Administrator"],
        "gidNumber": ["1007"],
        "uidNumber": ["1007"],
        "homeDirectory": ["/home/admin"],
        "loginShell": ["/bin/bash"],
        "objectClass":
        ["organizationalRole", "posixAccount", "simpleSecurityObject"],
        "userPassword": ["yunohost"],
    }

    ldap.update("cn=admin", admin_dict)

    # Force nscd to refresh cache to take admin creation into account
    subprocess.call(["nscd", "-i", "passwd"])

    # Check admin actually exists now
    try:
        pwd.getpwnam("admin")
    except KeyError:
        logger.error(m18n.n("ldap_init_failed_to_create_admin"))
        raise YunohostError("installation_failed")

    try:
        # Attempt to create user home folder
        subprocess.check_call(["mkhomedir_helper", "admin"])
    except subprocess.CalledProcessError:
        if not os.path.isdir("/home/{0}".format("admin")):
            logger.warning(m18n.n("user_home_creation_failed"), exc_info=1)

    logger.success(m18n.n("ldap_initialized"))
Exemple #24
0
def user_group_update(operation_logger,
                      groupname,
                      add=None,
                      remove=None,
                      force=False,
                      sync_perm=True):
    """
    Update user informations

    Keyword argument:
        groupname -- Groupname to update
        add -- User(s) to add in group
        remove -- User(s) to remove in group

    """

    from yunohost.permission import permission_sync_to_user
    from yunohost.utils.ldap import _get_ldap_interface

    existing_users = list(user_list()['users'].keys())

    # Refuse to edit a primary group of a user (e.g. group 'sam' related to user 'sam')
    # Those kind of group should only ever contain the user (e.g. sam) and only this one.
    # We also can't edit "all_users" without the force option because that's a special group...
    if not force:
        if groupname == "all_users":
            raise YunohostError('group_cannot_edit_all_users')
        elif groupname == "visitors":
            raise YunohostError('group_cannot_edit_visitors')
        elif groupname in existing_users:
            raise YunohostError('group_cannot_edit_primary_group',
                                group=groupname)

    # We extract the uid for each member of the group to keep a simple flat list of members
    current_group = user_group_info(groupname)["members"]
    new_group = copy.copy(current_group)

    if add:
        users_to_add = [add] if not isinstance(add, list) else add

        for user in users_to_add:
            if user not in existing_users:
                raise YunohostError('user_unknown', user=user)

            if user in current_group:
                logger.warning(
                    m18n.n('group_user_already_in_group',
                           user=user,
                           group=groupname))
            else:
                operation_logger.related_to.append(('user', user))

        new_group += users_to_add

    if remove:
        users_to_remove = [remove] if not isinstance(remove, list) else remove

        for user in users_to_remove:
            if user not in current_group:
                logger.warning(
                    m18n.n('group_user_not_in_group',
                           user=user,
                           group=groupname))
            else:
                operation_logger.related_to.append(('user', user))

        # Remove users_to_remove from new_group
        # Kinda like a new_group -= users_to_remove
        new_group = [u for u in new_group if u not in users_to_remove]

    new_group_dns = [
        "uid=" + user + ",ou=users,dc=yunohost,dc=org" for user in new_group
    ]

    if set(new_group) != set(current_group):
        operation_logger.start()
        ldap = _get_ldap_interface()
        try:
            ldap.update('cn=%s,ou=groups' % groupname, {
                "member": set(new_group_dns),
                "memberUid": set(new_group)
            })
        except Exception as e:
            raise YunohostError('group_update_failed',
                                group=groupname,
                                error=e)

    if groupname != "all_users":
        logger.success(m18n.n('group_updated', group=groupname))
    else:
        logger.debug(m18n.n('group_updated', group=groupname))

    if sync_perm:
        permission_sync_to_user()
    return user_group_info(groupname)
Exemple #25
0
def user_group_create(operation_logger,
                      groupname,
                      gid=None,
                      primary_group=False,
                      sync_perm=True):
    """
    Create group

    Keyword argument:
        groupname -- Must be unique

    """
    from yunohost.permission import permission_sync_to_user
    from yunohost.utils.ldap import _get_ldap_interface

    ldap = _get_ldap_interface()

    # Validate uniqueness of groupname in LDAP
    conflict = ldap.get_conflict({'cn': groupname},
                                 base_dn='ou=groups,dc=yunohost,dc=org')
    if conflict:
        raise YunohostError('group_already_exist', group=groupname)

    # Validate uniqueness of groupname in system group
    all_existing_groupnames = {x.gr_name for x in grp.getgrall()}
    if groupname in all_existing_groupnames:
        if primary_group:
            logger.warning(
                m18n.n('group_already_exist_on_system_but_removing_it',
                       group=groupname))
            subprocess.check_call("sed --in-place '/^%s:/d' /etc/group" %
                                  groupname,
                                  shell=True)
        else:
            raise YunohostError('group_already_exist_on_system',
                                group=groupname)

    if not gid:
        # Get random GID
        all_gid = {x.gr_gid for x in grp.getgrall()}

        uid_guid_found = False
        while not uid_guid_found:
            gid = str(random.randint(200, 99999))
            uid_guid_found = gid not in all_gid

    attr_dict = {
        'objectClass': ['top', 'groupOfNamesYnh', 'posixGroup'],
        'cn': groupname,
        'gidNumber': gid,
    }

    # Here we handle the creation of a primary group
    # We want to initialize this group to contain the corresponding user
    # (then we won't be able to add/remove any user in this group)
    if primary_group:
        attr_dict["member"] = [
            "uid=" + groupname + ",ou=users,dc=yunohost,dc=org"
        ]

    operation_logger.start()
    try:
        ldap.add('cn=%s,ou=groups' % groupname, attr_dict)
    except Exception as e:
        raise YunohostError('group_creation_failed', group=groupname, error=e)

    if sync_perm:
        permission_sync_to_user()

    if not primary_group:
        logger.success(m18n.n('group_created', group=groupname))
    else:
        logger.debug(m18n.n('group_created', group=groupname))

    return {'name': groupname}
Exemple #26
0
def user_info(username):
    """
    Get user informations

    Keyword argument:
        username -- Username or mail to get informations

    """
    from yunohost.utils.ldap import _get_ldap_interface

    ldap = _get_ldap_interface()

    user_attrs = [
        'cn', 'mail', 'uid', 'maildrop', 'givenName', 'sn', 'mailuserquota'
    ]

    if len(username.split('@')) == 2:
        filter = 'mail=' + username
    else:
        filter = 'uid=' + username

    result = ldap.search('ou=users,dc=yunohost,dc=org', filter, user_attrs)

    if result:
        user = result[0]
    else:
        raise YunohostError('user_unknown', user=username)

    result_dict = {
        'username': user['uid'][0],
        'fullname': user['cn'][0],
        'firstname': user['givenName'][0],
        'lastname': user['sn'][0],
        'mail': user['mail'][0]
    }

    if len(user['mail']) > 1:
        result_dict['mail-aliases'] = user['mail'][1:]

    if len(user['maildrop']) > 1:
        result_dict['mail-forward'] = user['maildrop'][1:]

    if 'mailuserquota' in user:
        userquota = user['mailuserquota'][0]

        if isinstance(userquota, int):
            userquota = str(userquota)

        # Test if userquota is '0' or '0M' ( quota pattern is ^(\d+[bkMGT])|0$ )
        is_limited = not re.match('0[bkMGT]?', userquota)
        storage_use = '?'

        if service_status("dovecot")["status"] != "running":
            logger.warning(m18n.n('mailbox_used_space_dovecot_down'))
        elif username not in user_permission_info(
                "mail.main")["corresponding_users"]:
            logger.warning(m18n.n('mailbox_disabled', user=username))
        else:
            try:
                cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0]
                cmd_result = check_output(cmd)
            except Exception as e:
                cmd_result = ""
                logger.warning("Failed to fetch quota info ... : %s " % str(e))

            # Exemple of return value for cmd:
            # """Quota name=User quota Type=STORAGE Value=0 Limit=- %=0
            # Quota name=User quota Type=MESSAGE Value=0 Limit=- %=0"""
            has_value = re.search(r'Value=(\d+)', cmd_result)

            if has_value:
                storage_use = int(has_value.group(1))
                storage_use = _convertSize(storage_use)

                if is_limited:
                    has_percent = re.search(r'%=(\d+)', cmd_result)

                    if has_percent:
                        percentage = int(has_percent.group(1))
                        storage_use += ' (%s%%)' % percentage

        result_dict['mailbox-quota'] = {
            'limit': userquota if is_limited else m18n.n('unlimit'),
            'use': storage_use
        }

    return result_dict
Exemple #27
0
def domain_add(operation_logger, domain, dyndns=False):
    """
    Create a custom domain

    Keyword argument:
        domain -- Domain name to add
        dyndns -- Subscribe to DynDNS

    """
    from yunohost.hook import hook_callback
    from yunohost.app import app_ssowatconf
    from yunohost.utils.ldap import _get_ldap_interface

    if domain.startswith("xmpp-upload."):
        raise YunohostError("domain_cannot_add_xmpp_upload")

    ldap = _get_ldap_interface()

    try:
        ldap.validate_uniqueness({"virtualdomain": domain})
    except MoulinetteError:
        raise YunohostError("domain_exists")

    operation_logger.start()

    # Lower domain to avoid some edge cases issues
    # See: https://forum.yunohost.org/t/invalid-domain-causes-diagnosis-web-to-fail-fr-on-demand/11765
    domain = domain.lower()

    # DynDNS domain
    if dyndns:

        # Do not allow to subscribe to multiple dyndns domains...
        if os.path.exists("/etc/cron.d/yunohost-dyndns"):
            raise YunohostError("domain_dyndns_already_subscribed")

        from yunohost.dyndns import dyndns_subscribe, _dyndns_provides

        # Check that this domain can effectively be provided by
        # dyndns.yunohost.org. (i.e. is it a nohost.me / noho.st)
        if not _dyndns_provides("dyndns.yunohost.org", domain):
            raise YunohostError("domain_dyndns_root_unknown")

        # Actually subscribe
        dyndns_subscribe(domain=domain)

    try:
        import yunohost.certificate

        yunohost.certificate._certificate_install_selfsigned([domain], False)

        attr_dict = {
            "objectClass": ["mailDomain", "top"],
            "virtualdomain": domain,
        }

        try:
            ldap.add("virtualdomain=%s,ou=domains" % domain, attr_dict)
        except Exception as e:
            raise YunohostError("domain_creation_failed",
                                domain=domain,
                                error=e)

        # Don't regen these conf if we're still in postinstall
        if os.path.exists("/etc/yunohost/installed"):
            # Sometime we have weird issues with the regenconf where some files
            # appears as manually modified even though they weren't touched ...
            # There are a few ideas why this happens (like backup/restore nginx
            # conf ... which we shouldnt do ...). This in turns creates funky
            # situation where the regenconf may refuse to re-create the conf
            # (when re-creating a domain..)
            # So here we force-clear the has out of the regenconf if it exists.
            # This is a pretty ad hoc solution and only applied to nginx
            # because it's one of the major service, but in the long term we
            # should identify the root of this bug...
            _force_clear_hashes(["/etc/nginx/conf.d/%s.conf" % domain])
            regen_conf(
                names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd"])
            app_ssowatconf()

    except Exception:
        # Force domain removal silently
        try:
            domain_remove(domain, force=True)
        except Exception:
            pass
        raise

    hook_callback("post_domain_add", args=[domain])

    logger.success(m18n.n("domain_created"))
Exemple #28
0
def user_update(operation_logger,
                username,
                firstname=None,
                lastname=None,
                mail=None,
                change_password=None,
                add_mailforward=None,
                remove_mailforward=None,
                add_mailalias=None,
                remove_mailalias=None,
                mailbox_quota=None):
    """
    Update user informations

    Keyword argument:
        lastname
        mail
        firstname
        add_mailalias -- Mail aliases to add
        remove_mailforward -- Mailforward addresses to remove
        username -- Username of user to update
        add_mailforward -- Mailforward addresses to add
        change_password -- New password to set
        remove_mailalias -- Mail aliases to remove

    """
    from yunohost.domain import domain_list, _get_maindomain
    from yunohost.app import app_ssowatconf
    from yunohost.utils.password import assert_password_is_strong_enough
    from yunohost.utils.ldap import _get_ldap_interface
    from yunohost.hook import hook_callback

    domains = domain_list()['domains']

    # Populate user informations
    ldap = _get_ldap_interface()
    attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop']
    result = ldap.search(base='ou=users,dc=yunohost,dc=org',
                         filter='uid=' + username,
                         attrs=attrs_to_fetch)
    if not result:
        raise YunohostError('user_unknown', user=username)
    user = result[0]
    env_dict = {"YNH_USER_USERNAME": username}

    # Get modifications from arguments
    new_attr_dict = {}
    if firstname:
        new_attr_dict['givenName'] = [firstname]  # TODO: Validate
        new_attr_dict['cn'] = new_attr_dict['displayName'] = [
            firstname + ' ' + user['sn'][0]
        ]
        env_dict["YNH_USER_FIRSTNAME"] = firstname

    if lastname:
        new_attr_dict['sn'] = [lastname]  # TODO: Validate
        new_attr_dict['cn'] = new_attr_dict['displayName'] = [
            user['givenName'][0] + ' ' + lastname
        ]
        env_dict["YNH_USER_LASTNAME"] = lastname

    if lastname and firstname:
        new_attr_dict['cn'] = new_attr_dict['displayName'] = [
            firstname + ' ' + lastname
        ]

    # change_password is None if user_update is not called to change the password
    if change_password is not None:
        # when in the cli interface if the option to change the password is called
        # without a specified value, change_password will be set to the const 0.
        # In this case we prompt for the new password.
        if msettings.get('interface') == 'cli' and not change_password:
            change_password = msignals.prompt(m18n.n("ask_password"), True,
                                              True)
        # Ensure sufficiently complex password
        assert_password_is_strong_enough("user", change_password)

        new_attr_dict['userPassword'] = [_hash_user_password(change_password)]
        env_dict["YNH_USER_PASSWORD"] = change_password

    if mail:
        main_domain = _get_maindomain()
        aliases = [
            'root@' + main_domain,
            'admin@' + main_domain,
            'webmaster@' + main_domain,
            'postmaster@' + main_domain,
        ]
        try:
            ldap.validate_uniqueness({'mail': mail})
        except Exception as e:
            raise YunohostError('user_update_failed', user=username, error=e)
        if mail[mail.find('@') + 1:] not in domains:
            raise YunohostError('mail_domain_unknown',
                                domain=mail[mail.find('@') + 1:])
        if mail in aliases:
            raise YunohostError('mail_unavailable')

        del user['mail'][0]
        new_attr_dict['mail'] = [mail] + user['mail']

    if add_mailalias:
        if not isinstance(add_mailalias, list):
            add_mailalias = [add_mailalias]
        for mail in add_mailalias:
            try:
                ldap.validate_uniqueness({'mail': mail})
            except Exception as e:
                raise YunohostError('user_update_failed',
                                    user=username,
                                    error=e)
            if mail[mail.find('@') + 1:] not in domains:
                raise YunohostError('mail_domain_unknown',
                                    domain=mail[mail.find('@') + 1:])
            user['mail'].append(mail)
        new_attr_dict['mail'] = user['mail']

    if remove_mailalias:
        if not isinstance(remove_mailalias, list):
            remove_mailalias = [remove_mailalias]
        for mail in remove_mailalias:
            if len(user['mail']) > 1 and mail in user['mail'][1:]:
                user['mail'].remove(mail)
            else:
                raise YunohostError('mail_alias_remove_failed', mail=mail)
        new_attr_dict['mail'] = user['mail']

    if 'mail' in new_attr_dict:
        env_dict["YNH_USER_MAILS"] = ','.join(new_attr_dict['mail'])

    if add_mailforward:
        if not isinstance(add_mailforward, list):
            add_mailforward = [add_mailforward]
        for mail in add_mailforward:
            if mail in user['maildrop'][1:]:
                continue
            user['maildrop'].append(mail)
        new_attr_dict['maildrop'] = user['maildrop']

    if remove_mailforward:
        if not isinstance(remove_mailforward, list):
            remove_mailforward = [remove_mailforward]
        for mail in remove_mailforward:
            if len(user['maildrop']) > 1 and mail in user['maildrop'][1:]:
                user['maildrop'].remove(mail)
            else:
                raise YunohostError('mail_forward_remove_failed', mail=mail)
        new_attr_dict['maildrop'] = user['maildrop']

    if 'maildrop' in new_attr_dict:
        env_dict["YNH_USER_MAILFORWARDS"] = ','.join(new_attr_dict['maildrop'])

    if mailbox_quota is not None:
        new_attr_dict['mailuserquota'] = [mailbox_quota]
        env_dict["YNH_USER_MAILQUOTA"] = mailbox_quota

    operation_logger.start()

    try:
        ldap.update('uid=%s,ou=users' % username, new_attr_dict)
    except Exception as e:
        raise YunohostError('user_update_failed', user=username, error=e)

    # Trigger post_user_update hooks
    hook_callback('post_user_update', env=env_dict)

    logger.success(m18n.n('user_updated'))
    app_ssowatconf()
    return user_info(username)
Exemple #29
0
def _update_ldap_group_permission(permission,
                                  allowed,
                                  label=None,
                                  show_tile=None,
                                  protected=None,
                                  sync_perm=True):
    """
    Internal function that will rewrite user permission

    permission      -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
    allowed         -- (optional) A list of group/user to allow for the permission
    label           -- (optional) Define a name for the permission. This label will be shown on the SSO and in the admin
    show_tile       -- (optional) Define if a tile will be shown in the SSO
    protected       -- (optional) Define if the permission can be added/removed to the visitor group


    Assumptions made, that should be checked before calling this function:
    - the permission does currently exists ...
    - the 'allowed' list argument is *different* from the current
      permission state ... otherwise ldap will miserably fail in such
      case...
    - the 'allowed' list contains *existing* groups.
    """

    from yunohost.hook import hook_callback
    from yunohost.utils.ldap import _get_ldap_interface

    ldap = _get_ldap_interface()

    existing_permission = user_permission_info(permission)

    update = {}

    if allowed is not None:
        allowed = [allowed] if not isinstance(allowed, list) else allowed
        # Guarantee uniqueness of values in allowed, which would otherwise make ldap.update angry.
        allowed = set(allowed)
        update["groupPermission"] = [
            "cn=" + g + ",ou=groups,dc=yunohost,dc=org" for g in allowed
        ]

    if label is not None:
        update["label"] = [str(label)]

    if protected is not None:
        update["isProtected"] = [str(protected).upper()]

    if show_tile is not None:

        if show_tile is True:
            if not existing_permission["url"]:
                logger.warning(
                    m18n.n(
                        "show_tile_cant_be_enabled_for_url_not_defined",
                        permission=permission,
                    ))
                show_tile = False
            elif existing_permission["url"].startswith("re:"):
                logger.warning(
                    m18n.n("show_tile_cant_be_enabled_for_regex",
                           permission=permission))
                show_tile = False
        update["showTile"] = [str(show_tile).upper()]

    try:
        ldap.update("cn=%s,ou=permission" % permission, update)
    except Exception as e:
        raise YunohostError("permission_update_failed",
                            permission=permission,
                            error=e)

    # Trigger permission sync if asked

    if sync_perm:
        permission_sync_to_user()

    new_permission = user_permission_info(permission)

    # Trigger app callbacks

    app = permission.split(".")[0]
    sub_permission = permission.split(".")[1]

    old_corresponding_users = set(existing_permission["corresponding_users"])
    new_corresponding_users = set(new_permission["corresponding_users"])

    old_allowed_users = set(existing_permission["allowed"])
    new_allowed_users = set(new_permission["allowed"])

    effectively_added_users = new_corresponding_users - old_corresponding_users
    effectively_removed_users = old_corresponding_users - new_corresponding_users

    effectively_added_group = (new_allowed_users - old_allowed_users -
                               effectively_added_users)
    effectively_removed_group = (old_allowed_users - new_allowed_users -
                                 effectively_removed_users)

    if effectively_added_users or effectively_added_group:
        hook_callback(
            "post_app_addaccess",
            args=[
                app,
                ",".join(effectively_added_users),
                sub_permission,
                ",".join(effectively_added_group),
            ],
        )
    if effectively_removed_users or effectively_removed_group:
        hook_callback(
            "post_app_removeaccess",
            args=[
                app,
                ",".join(effectively_removed_users),
                sub_permission,
                ",".join(effectively_removed_group),
            ],
        )

    return new_permission
Exemple #30
0
def check_LDAP_db_integrity():
    # Here we check that all attributes in all object are sychronized.
    # Here is the list of attributes per object:
    # user : memberOf, permission
    # group : member, permission
    # permission : groupPermission, inheritPermission
    #
    # The idea is to check that all attributes on all sides of object are sychronized.
    # One part should be done automatically by the "memberOf" overlay of LDAP.
    # The other part is done by the the "permission_sync_to_user" function of the permission module

    from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
    ldap = _get_ldap_interface()

    user_search = ldap.search(
        'ou=users,dc=yunohost,dc=org',
        '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))',
        ['uid', 'memberOf', 'permission'])
    group_search = ldap.search('ou=groups,dc=yunohost,dc=org',
                               '(objectclass=groupOfNamesYnh)',
                               ['cn', 'member', 'memberUid', 'permission'])
    permission_search = ldap.search(
        'ou=permission,dc=yunohost,dc=org', '(objectclass=permissionYnh)',
        ['cn', 'groupPermission', 'inheritPermission', 'memberUid'])

    user_map = {u['uid'][0]: u for u in user_search}
    group_map = {g['cn'][0]: g for g in group_search}
    permission_map = {p['cn'][0]: p for p in permission_search}

    for user in user_search:
        user_dn = 'uid=' + user['uid'][0] + ',ou=users,dc=yunohost,dc=org'
        group_list = [_ldap_path_extract(m, "cn") for m in user['memberOf']]
        permission_list = [
            _ldap_path_extract(m, "cn") for m in user.get('permission', [])
        ]

        # This user's DN sould be found in all groups it is a member of
        for group in group_list:
            assert user_dn in group_map[group]['member']

        # This user's DN should be found in all perms it has access to
        for permission in permission_list:
            assert user_dn in permission_map[permission]['inheritPermission']

    for permission in permission_search:
        permission_dn = 'cn=' + permission['cn'][
            0] + ',ou=permission,dc=yunohost,dc=org'

        # inheritPermission uid's should match memberUids
        user_list = [
            _ldap_path_extract(m, "uid")
            for m in permission.get('inheritPermission', [])
        ]
        assert set(user_list) == set(permission.get('memberUid', []))

        # This perm's DN should be found on all related users it is related to
        for user in user_list:
            assert permission_dn in user_map[user]['permission']

        # Same for groups : we should find the permission's DN for all related groups
        group_list = [
            _ldap_path_extract(m, "cn")
            for m in permission.get('groupPermission', [])
        ]
        for group in group_list:
            assert permission_dn in group_map[group]['permission']

            # The list of user in the group should be a subset of all users related to the current permission
            users_in_group = [
                _ldap_path_extract(m, "uid")
                for m in group_map[group].get("member", [])
            ]
            assert set(users_in_group) <= set(user_list)

    for group in group_search:
        group_dn = 'cn=' + group['cn'][0] + ',ou=groups,dc=yunohost,dc=org'

        user_list = [
            _ldap_path_extract(m, "uid") for m in group.get("member", [])
        ]
        # For primary groups, we should find that :
        #    - len(user_list) is 1 (a primary group has only 1 member)
        #    - the group name should be an existing yunohost user
        #    - memberUid is empty (meaning no other member than the corresponding user)
        if group['cn'][0] in user_list:
            assert len(user_list) == 1
            assert group["cn"][0] in user_map
            assert group.get('memberUid', []) == []
        # Otherwise, user_list and memberUid should have the same content
        else:
            assert set(user_list) == set(group.get('memberUid', []))

        # For all users members, this group should be in the "memberOf" on the other side
        for user in user_list:
            assert group_dn in user_map[user]['memberOf']

        # For all the permissions of this group, the group should be among the "groupPermission" on the other side
        permission_list = [
            _ldap_path_extract(m, "cn") for m in group.get('permission', [])
        ]
        for permission in permission_list:
            assert group_dn in permission_map[permission]['groupPermission']

            # And the list of user of this group (user_list) should be a subset of all allowed users for this perm...
            allowed_user_list = [
                _ldap_path_extract(m, "uid")
                for m in permission_map[permission].get(
                    'inheritPermission', [])
            ]
            assert set(user_list) <= set(allowed_user_list)