mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge pull request #861 from YunoHost/permission_protection
Extends permissions features
This commit is contained in:
commit
c45c182980
17 changed files with 1680 additions and 499 deletions
|
@ -330,6 +330,15 @@ user:
|
|||
metavar: GROUP_OR_USER
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
-l:
|
||||
full: --label
|
||||
help: Label for this permission. This label will be shown on the SSO and in the admin
|
||||
-s:
|
||||
full: --show_tile
|
||||
help: Define if a tile will be shown in the SSO
|
||||
choices:
|
||||
- 'True'
|
||||
- 'False'
|
||||
|
||||
## user_permission_reset()
|
||||
reset:
|
||||
|
|
|
@ -16,7 +16,11 @@ ynh_app_setting_get() {
|
|||
# Manage arguments with getopts
|
||||
ynh_handle_getopts_args "$@"
|
||||
|
||||
ynh_app_setting "get" "$app" "$key"
|
||||
if [[ $key =~ (unprotected|protected|skipped)_ ]]; then
|
||||
yunohost app setting $app $key
|
||||
else
|
||||
ynh_app_setting "get" "$app" "$key"
|
||||
fi
|
||||
}
|
||||
|
||||
# Set an application setting
|
||||
|
@ -37,7 +41,11 @@ ynh_app_setting_set() {
|
|||
# Manage arguments with getopts
|
||||
ynh_handle_getopts_args "$@"
|
||||
|
||||
ynh_app_setting "set" "$app" "$key" "$value"
|
||||
if [[ $key =~ (unprotected|protected|skipped)_ ]]; then
|
||||
yunohost app setting $app $key -v $value
|
||||
else
|
||||
ynh_app_setting "set" "$app" "$key" "$value"
|
||||
fi
|
||||
}
|
||||
|
||||
# Delete an application setting
|
||||
|
@ -56,7 +64,11 @@ ynh_app_setting_delete() {
|
|||
# Manage arguments with getopts
|
||||
ynh_handle_getopts_args "$@"
|
||||
|
||||
ynh_app_setting "delete" "$app" "$key"
|
||||
if [[ "$key" =~ (unprotected|skipped|protected)_ ]]; then
|
||||
yunohost app setting $app $key -d
|
||||
else
|
||||
ynh_app_setting "delete" "$app" "$key"
|
||||
fi
|
||||
}
|
||||
|
||||
# Small "hard-coded" interface to avoid calling "yunohost app" directly each
|
||||
|
@ -66,11 +78,6 @@ ynh_app_setting_delete() {
|
|||
#
|
||||
ynh_app_setting()
|
||||
{
|
||||
if [[ "$1" == "delete" ]] && [[ "$3" =~ ^(unprotected|skipped)_ ]]
|
||||
then
|
||||
current_value=$(ynh_app_setting_get --app=$app --key=$3)
|
||||
fi
|
||||
|
||||
ACTION="$1" APP="$2" KEY="$3" VALUE="${4:-}" python2.7 - <<EOF
|
||||
import os, yaml, sys
|
||||
app, action = os.environ['APP'], os.environ['ACTION'].lower()
|
||||
|
@ -89,27 +96,12 @@ else:
|
|||
elif action == "set":
|
||||
if key in ['redirected_urls', 'redirected_regex']:
|
||||
value = yaml.load(value)
|
||||
if any(key.startswith(word+"_") for word in ["unprotected", "protected", "skipped"]):
|
||||
sys.stderr.write("/!\\ Packagers! This app is still using the skipped/protected/unprotected_uris/regex settings which are now obsolete and deprecated... Instead, you should use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to initialize the public/private access. Check out the documentation at the bottom of yunohost.org/groups_and_permissions to learn how to use the new permission mechanism.\n")
|
||||
settings[key] = value
|
||||
else:
|
||||
raise ValueError("action should either be get, set or delete")
|
||||
with open(setting_file, "w") as f:
|
||||
yaml.safe_dump(settings, f, default_flow_style=False)
|
||||
EOF
|
||||
|
||||
# Fucking legacy permission management.
|
||||
# We need this because app temporarily set the app as unprotected to configure it with curl...
|
||||
if [[ "$3" =~ ^(unprotected|skipped)_ ]]
|
||||
then
|
||||
if [[ "$1" == "set" ]] && [[ "${4:-}" == "/" ]]
|
||||
then
|
||||
ynh_permission_update --permission "main" --add "visitors"
|
||||
elif [[ "$1" == "delete" ]] && [[ "${current_value:-}" == "/" ]] && [[ -n "$(ynh_app_setting_get --app=$2 --key='is_public' )" ]]
|
||||
then
|
||||
ynh_permission_update --permission "main" --remove "visitors"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Check availability of a web path
|
||||
|
@ -158,48 +150,155 @@ ynh_webpath_register () {
|
|||
|
||||
# Create a new permission for the app
|
||||
#
|
||||
# example: ynh_permission_create --permission=admin --url=/admin --allowed="alice bob"
|
||||
# example 1: ynh_permission_create --permission=admin --url=/admin --additional_urls=domain.tld/admin /superadmin --allowed=alice bob \
|
||||
# --label="My app admin" --show_tile=true
|
||||
#
|
||||
# usage: ynh_permission_create --permission "permission" [--url=url] [--allowed="group1 group2"]
|
||||
# | arg: -p, --permission= - the name for the permission (by default a permission named "main" already exist)
|
||||
# | arg: -u, --url= - (optional) URL for which access will be allowed/forbidden
|
||||
# | arg: -a, --allowed= - (optional) A list of group/user to allow for the permission
|
||||
# This example will create a new permission permission with this following effect:
|
||||
# - A tile named "My app admin" in the SSO will be available for the users alice and bob. This tile will point to the relative url '/admin'.
|
||||
# - Only the user alice and bob will have the access to theses following url: /admin, domain.tld/admin, /superadmin
|
||||
#
|
||||
# If provided, 'url' is assumed to be relative to the app domain/path if they
|
||||
#
|
||||
# example 2: ynh_permission_create --permission=api --url=domain.tld/api --auth_header=false --allowed=visitors \
|
||||
# --label="MyApp API" --protected=true
|
||||
#
|
||||
# This example will create a new protected permission. So the admin won't be able to add/remove the visitors group of this permission.
|
||||
# In case of an API with need to be always public it avoid that the admin break anything.
|
||||
# With this permission all client will be allowed to access to the url 'domain.tld/api'.
|
||||
# Note that in this case no tile will be show on the SSO.
|
||||
# Note that the auth_header parameter is to 'false'. So no authentication header will be passed to the application.
|
||||
# Generally the API is requested by an application and enabling the auth_header has no advantage and could bring some issues in some case.
|
||||
# So in this case it's better to disable this option for all API.
|
||||
#
|
||||
#
|
||||
# usage: ynh_permission_create --permission="permission" [--url="url"] [--additional_urls="second-url" [ "third-url" ]] [--auth_header=true|false]
|
||||
# [--allowed=group1 [ group2 ]] [--label="label"] [--show_tile=true|false]
|
||||
# [--protected=true|false]
|
||||
# | arg: -p, permission= - the name for the permission (by default a permission named "main" already exist)
|
||||
# | arg: -u, url= - (optional) URL for which access will be allowed/forbidden.
|
||||
# | Not that if 'show_tile' is enabled, this URL will be the URL of the tile.
|
||||
# | arg: -A, additional_urls= - (optional) List of additional URL for which access will be allowed/forbidden
|
||||
# | arg: -h, auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application. Default is true
|
||||
# | arg: -a, allowed= - (optional) A list of group/user to allow for the permission
|
||||
# | arg: -l, label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin.
|
||||
# | Default is "APP_LABEL (permission name)".
|
||||
# | arg: -t, show_tile= - (optional) Define if a tile will be shown in the SSO. If yes the name of the tile will be the 'label' parameter.
|
||||
# | Default is false (for the permission different than 'main').
|
||||
# | arg: -P, protected= - (optional) Define if this permission is protected. If it is protected the administrator
|
||||
# | won't be able to add or remove the visitors group of this permission.
|
||||
# | By default it's 'false'
|
||||
#
|
||||
# If provided, 'url' or 'additional_urls' 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:".
|
||||
# 'url' or 'additional_urls' can be treated as a PCRE (not lua) 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]*$
|
||||
#
|
||||
# Note that globally the parameter 'url' and 'additional_urls' are same. The only difference is:
|
||||
# - 'url' is only one url, 'additional_urls' can be a list of urls. There are no limitation of 'additional_urls'
|
||||
# - 'url' is used for the url of tile in the SSO (if enabled with the 'show_tile' parameter)
|
||||
#
|
||||
#
|
||||
# About the authentication header (auth_header parameter).
|
||||
# The SSO pass (by default) to the application theses following HTTP header (linked to the authenticated user) to the application:
|
||||
# - "Auth-User": username
|
||||
# - "Remote-User": username
|
||||
# - "Email": user email
|
||||
#
|
||||
# Generally this feature is usefull to authenticate automatically the user in the application but in some case the application don't work with theses header and theses header need to be disabled to have the application to work correctly.
|
||||
# See https://github.com/YunoHost/issues/issues/1420 for more informations
|
||||
#
|
||||
#
|
||||
# Requires YunoHost version 3.7.0 or higher.
|
||||
ynh_permission_create() {
|
||||
# Declare an array to define the options of this helper.
|
||||
local legacy_args=pua
|
||||
local -A args_array=( [p]=permission= [u]=url= [a]=allowed= )
|
||||
local legacy_args=puAhaltP
|
||||
local -A args_array=( [p]=permission= [u]=url= [A]=additional_urls= [h]=auth_header= [a]=allowed= [l]=label= [t]=show_tile= [P]=protected= )
|
||||
local permission
|
||||
local url
|
||||
local additional_urls
|
||||
local auth_header
|
||||
local allowed
|
||||
local label
|
||||
local show_tile
|
||||
local protected
|
||||
ynh_handle_getopts_args "$@"
|
||||
url=${url:-}
|
||||
additional_urls=${additional_urls:-}
|
||||
auth_header=${auth_header:-}
|
||||
allowed=${allowed:-}
|
||||
label=${label:-}
|
||||
show_tile=${show_tile:-}
|
||||
protected=${protected:-}
|
||||
|
||||
if [[ -n $url ]]
|
||||
then
|
||||
url="'$url'"
|
||||
else
|
||||
url="None"
|
||||
url=",url='$url'"
|
||||
fi
|
||||
|
||||
if [[ -n $allowed ]]; then
|
||||
allowed=",allowed=['${allowed//';'/"','"}']"
|
||||
if [[ -n $additional_urls ]]
|
||||
then
|
||||
# Convert a list from getopts to python list
|
||||
# Note that getopts separate the args with ';'
|
||||
# By example:
|
||||
# --additional_urls /urlA /urlB
|
||||
# will be:
|
||||
# additional_urls=['/urlA', '/urlB']
|
||||
additional_urls=",additional_urls=['${additional_urls//;/\',\'}']"
|
||||
fi
|
||||
|
||||
yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url $allowed , sync_perm=False)"
|
||||
|
||||
if [[ -n $auth_header ]]
|
||||
then
|
||||
if [ $auth_header == "true" ]
|
||||
then
|
||||
auth_header=",auth_header=True"
|
||||
else
|
||||
auth_header=",auth_header=False"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n $allowed ]]
|
||||
then
|
||||
# Convert a list from getopts to python list
|
||||
# Note that getopts separate the args with ';'
|
||||
# By example:
|
||||
# --allowed alice bob
|
||||
# will be:
|
||||
# allowed=['alice', 'bob']
|
||||
allowed=",allowed=['${allowed//;/\',\'}']"
|
||||
fi
|
||||
|
||||
if [[ -n ${label:-} ]]; then
|
||||
label=",label='$label'"
|
||||
else
|
||||
label=",label='$permission'"
|
||||
fi
|
||||
|
||||
if [[ -n ${show_tile:-} ]]
|
||||
then
|
||||
if [ $show_tile == "true" ]
|
||||
then
|
||||
show_tile=",show_tile=True"
|
||||
else
|
||||
show_tile=",show_tile=False"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n ${protected:-} ]]
|
||||
then
|
||||
if [ $protected == "true" ]
|
||||
then
|
||||
protected=",protected=True"
|
||||
else
|
||||
protected=",protected=False"
|
||||
fi
|
||||
fi
|
||||
|
||||
yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission' $url $additional_urls $auth_header $allowed $label $show_tile $protected , sync_perm=False)"
|
||||
}
|
||||
|
||||
# Remove a permission for the app (note that when the app is removed all permission is automatically removed)
|
||||
|
@ -239,60 +338,156 @@ ynh_permission_exists() {
|
|||
|
||||
# Redefine the url associated to a permission
|
||||
#
|
||||
# usage: ynh_permission_url --permission="permission" [--url="url"]
|
||||
# | arg: -p, --permission= - the name for the permission (by default a permission named "main" is removed automatically when the app is removed)
|
||||
# | arg: -u, --url= - (optional) URL for which access will be allowed/forbidden
|
||||
# usage: ynh_permission_url --permission "permission" [--url="url"] [--add_url="new-url" [ "other-new-url" ]] [--remove_url="old-url" [ "other-old-url" ]]
|
||||
# [--auth_header=true|false] [--clear_urls]
|
||||
# | arg: -p, permission= - the name for the permission (by default a permission named "main" is removed automatically when the app is removed)
|
||||
# | arg: -u, url= - (optional) URL for which access will be allowed/forbidden.
|
||||
# | Note that if you want to remove url you can pass an empty sting as arguments ("").
|
||||
# | arg: -a, add_url= - (optional) List of additional url to add for which access will be allowed/forbidden.
|
||||
# | arg: -r, remove_url= - (optional) List of additional url to remove for which access will be allowed/forbidden
|
||||
# | arg: -h, auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application
|
||||
# | arg: -c, clear_urls - (optional) Clean all urls (url and additional_urls)
|
||||
#
|
||||
# Requires YunoHost version 3.7.0 or higher.
|
||||
ynh_permission_url() {
|
||||
# Declare an array to define the options of this helper.
|
||||
local legacy_args=pu
|
||||
local -A args_array=([p]=permission= [u]=url=)
|
||||
local legacy_args=puarhc
|
||||
local -A args_array=( [p]=permission= [u]=url= [a]=add_url= [r]=remove_url= [h]=auth_header= [c]=clear_urls )
|
||||
local permission
|
||||
local url
|
||||
local add_url
|
||||
local remove_url
|
||||
local auth_header
|
||||
local clear_urls
|
||||
ynh_handle_getopts_args "$@"
|
||||
url=${url:-}
|
||||
add_url=${add_url:-}
|
||||
remove_url=${remove_url:-}
|
||||
auth_header=${auth_header:-}
|
||||
clear_urls=${clear_urls:-}
|
||||
|
||||
if [[ -n $url ]]
|
||||
then
|
||||
url="'$url'"
|
||||
else
|
||||
url="None"
|
||||
url=",url='$url'"
|
||||
fi
|
||||
|
||||
yunohost tools shell -c "from yunohost.permission import permission_url; permission_url('$app.$permission', url=$url)"
|
||||
if [[ -n $add_url ]]
|
||||
then
|
||||
# Convert a list from getopts to python list
|
||||
# Note that getopts separate the args with ';'
|
||||
# For example:
|
||||
# --add_url /urlA /urlB
|
||||
# will be:
|
||||
# add_url=['/urlA', '/urlB']
|
||||
add_url=",add_url=['${add_url//;/\',\'}']"
|
||||
fi
|
||||
|
||||
if [[ -n $remove_url ]]
|
||||
then
|
||||
# Convert a list from getopts to python list
|
||||
# Note that getopts separate the args with ';'
|
||||
# For example:
|
||||
# --remove_url /urlA /urlB
|
||||
# will be:
|
||||
# remove_url=['/urlA', '/urlB']
|
||||
remove_url=",remove_url=['${remove_url//;/\',\'}']"
|
||||
fi
|
||||
|
||||
if [[ -n $auth_header ]]
|
||||
then
|
||||
if [ $auth_header == "true" ]
|
||||
then
|
||||
auth_header=",auth_header=True"
|
||||
else
|
||||
auth_header=",auth_header=False"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n $clear_urls ]] && [ $clear_urls -eq 1 ]
|
||||
then
|
||||
clear_urls=",clear_urls=True"
|
||||
fi
|
||||
|
||||
yunohost tools shell -c "from yunohost.permission import permission_url; permission_url('$app.$permission' $url $add_url $remove_url $auth_header $clear_urls )"
|
||||
}
|
||||
|
||||
|
||||
# Update a permission for the app
|
||||
#
|
||||
# example: ynh_permission_update --permission admin --add=samdoe --remove=all_users
|
||||
#
|
||||
# usage: ynh_permission_update --permission="permission" [--add="group1 group2"] [--remove="group1 group2"]
|
||||
# | arg: -p, --permission= - the name for the permission (by default a permission named "main" already exist)
|
||||
# | arg: -a, --add= - the list of group or users to enable add to the permission
|
||||
# | arg: -r, --remove= - the list of group or users to remove from the permission
|
||||
# usage: ynh_permission_update --permission "permission" [--add="group" ["group" ...]] [--remove="group" ["group" ...]]
|
||||
# [--label="label"] [--show_tile=true|false] [--protected=true|false]
|
||||
# | arg: -p, permission= - the name for the permission (by default a permission named "main" already exist)
|
||||
# | arg: -a, add= - the list of group or users to enable add to the permission
|
||||
# | arg: -r, remove= - the list of group or users to remove from the permission
|
||||
# | arg: -l, label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin.
|
||||
# | arg: -t, show_tile= - (optional) Define if a tile will be shown in the SSO
|
||||
# | arg: -P, protected= - (optional) Define if this permission is protected. If it is protected the administrator
|
||||
# | won't be able to add or remove the visitors group of this permission.
|
||||
#
|
||||
# Requires YunoHost version 3.7.0 or higher.
|
||||
ynh_permission_update() {
|
||||
# Declare an array to define the options of this helper.
|
||||
local legacy_args=par
|
||||
local -A args_array=( [p]=permission= [a]=add= [r]=remove= )
|
||||
local legacy_args=parltP
|
||||
local -A args_array=( [p]=permission= [a]=add= [r]=remove= [l]=label= [t]=show_tile= [P]=protected= )
|
||||
local permission
|
||||
local add
|
||||
local remove
|
||||
local label
|
||||
local show_tile
|
||||
local protected
|
||||
ynh_handle_getopts_args "$@"
|
||||
add=${add:-}
|
||||
remove=${remove:-}
|
||||
label=${label:-}
|
||||
show_tile=${show_tile:-}
|
||||
protected=${protected:-}
|
||||
|
||||
if [[ -n $add ]]; then
|
||||
add="--add ${add//';'/" "}"
|
||||
if [[ -n $add ]]
|
||||
then
|
||||
# Convert a list from getopts to python list
|
||||
# Note that getopts separate the args with ';'
|
||||
# For example:
|
||||
# --add alice bob
|
||||
# will be:
|
||||
# add=['alice', 'bob']
|
||||
add=",add=['${add//';'/"','"}']"
|
||||
fi
|
||||
if [[ -n $remove ]]; then
|
||||
remove="--remove ${remove//';'/" "} "
|
||||
if [[ -n $remove ]]
|
||||
then
|
||||
# Convert a list from getopts to python list
|
||||
# Note that getopts separate the args with ';'
|
||||
# For example:
|
||||
# --remove alice bob
|
||||
# will be:
|
||||
# remove=['alice', 'bob']
|
||||
remove=",remove=['${remove//';'/"','"}']"
|
||||
fi
|
||||
|
||||
yunohost user permission update "$app.$permission" $add $remove
|
||||
if [[ -n $label ]]
|
||||
then
|
||||
label=",label='$label'"
|
||||
fi
|
||||
|
||||
if [[ -n $show_tile ]]
|
||||
then
|
||||
if [ $show_tile == "true" ]
|
||||
then
|
||||
show_tile=",show_tile=True"
|
||||
else
|
||||
show_tile=",show_tile=False"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n $protected ]]; then
|
||||
if [ $protected == "true" ]
|
||||
then
|
||||
protected=",protected=True"
|
||||
else
|
||||
protected=",protected=False"
|
||||
fi
|
||||
fi
|
||||
|
||||
yunohost tools shell -c "from yunohost.permission import user_permission_update; user_permission_update('$app.$permission' $add $remove $label $show_tile $protected , force=True, sync_perm=False)"
|
||||
}
|
||||
|
||||
# Check if a permission has an user
|
||||
|
|
|
@ -73,6 +73,10 @@ depends_children:
|
|||
- permissionYnh
|
||||
groupPermission:
|
||||
- "cn=all_users,ou=groups,dc=yunohost,dc=org"
|
||||
authHeader: "FALSE"
|
||||
label: "E-mail"
|
||||
showTile: "FALSE"
|
||||
isProtected: "TRUE"
|
||||
cn=xmpp.main,ou=permission:
|
||||
cn: xmpp.main
|
||||
gidNumber: "5002"
|
||||
|
@ -81,3 +85,7 @@ depends_children:
|
|||
- permissionYnh
|
||||
groupPermission:
|
||||
- "cn=all_users,ou=groups,dc=yunohost,dc=org"
|
||||
authHeader: "FALSE"
|
||||
label: "XMPP"
|
||||
showTile: "FALSE"
|
||||
isProtected: "TRUE"
|
||||
|
|
|
@ -15,8 +15,23 @@ olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.3 NAME 'inheritPermission'
|
|||
DESC 'Yunohost permission for user on permission side'
|
||||
SUP distinguishedName )
|
||||
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.4 NAME 'URL'
|
||||
DESC 'Yunohost application URL'
|
||||
DESC 'Yunohost permission main URL'
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} SINGLE-VALUE )
|
||||
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.5 NAME 'additionalUrls'
|
||||
DESC 'Yunohost permission additionnal URL'
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
|
||||
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.6 NAME 'authHeader'
|
||||
DESC 'Yunohost application, enable authentication header'
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
|
||||
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.7 NAME 'label'
|
||||
DESC 'Yunohost permission label, also used for the tile name in the SSO'
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} SINGLE-VALUE )
|
||||
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.8 NAME 'showTile'
|
||||
DESC 'Yunohost application, show/hide the tile in the SSO for this permission'
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
|
||||
olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.9 NAME 'isProtected'
|
||||
DESC 'Yunohost application permission protection'
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
|
||||
# OBJECTCLASS
|
||||
# For Applications
|
||||
olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh'
|
||||
|
@ -26,8 +41,8 @@ olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh'
|
|||
olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.2 NAME 'permissionYnh'
|
||||
DESC 'a Yunohost application'
|
||||
SUP top AUXILIARY
|
||||
MUST cn
|
||||
MAY ( groupPermission $ inheritPermission $ URL ) )
|
||||
MUST ( cn $ authHeader $ label $ showTile $ isProtected )
|
||||
MAY ( groupPermission $ inheritPermission $ URL $ additionalUrls ) )
|
||||
# For User
|
||||
olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.3 NAME 'userPermissionYnh'
|
||||
DESC 'a Yunohost application'
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
{
|
||||
"aborting": "Aborting.",
|
||||
"action_invalid": "Invalid action '{action:s}'",
|
||||
"additional_urls_already_added": "Additionnal URL '{url:s}' already added in the additional URL for permission '{permission:s}'",
|
||||
"additional_urls_already_removed": "Additionnal URL '{url:s}' already removed in the additional URL for permission '{permission:s}'",
|
||||
"admin_password": "Administration password",
|
||||
"admin_password_change_failed": "Cannot change password",
|
||||
"admin_password_change_failed": "Unable to change password",
|
||||
"admin_password_changed": "The administration password was changed",
|
||||
"admin_password_too_long": "Please choose a password shorter than 127 characters",
|
||||
"already_up_to_date": "Nothing to do. Everything is already up-to-date.",
|
||||
|
@ -23,9 +25,10 @@
|
|||
"app_full_domain_unavailable": "Sorry, this app must be installed on a domain of its own, but other apps are already installed on the domain '{domain}'. You could use a subdomain dedicated to this app instead.",
|
||||
"app_id_invalid": "Invalid app ID",
|
||||
"app_install_files_invalid": "These files cannot be installed",
|
||||
"app_install_failed": "Could not install {app}: {error}",
|
||||
"app_install_failed": "Unable to install {app}: {error}",
|
||||
"app_install_script_failed": "An error occurred inside the app installation script",
|
||||
"app_make_default_location_already_used": "Can't make the app '{app}' the default on the domain, '{domain}' is already in use by the other app '{other_app}'",
|
||||
"app_make_default_location_already_used": "Unable to make '{app}' the default app on the domain, '{domain}' is already in use by '{other_app}'",
|
||||
"app_label_deprecated": "This command is deprecated! Please use the new command 'yunohost user permission update' to manage the app label.",
|
||||
"app_location_unavailable": "This URL is either unavailable, or conflicts with the already installed app(s):\n{apps:s}",
|
||||
"app_manifest_invalid": "Something is wrong with the app manifest: {error}",
|
||||
"app_manifest_install_ask_domain": "Choose the domain where this app should be installed",
|
||||
|
@ -128,7 +131,6 @@
|
|||
"certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use '--force' to do so.)",
|
||||
"certmanager_domain_dns_ip_differs_from_public_ip": "The DNS records for domain '{domain:s}' is different from this server's IP. Please check the 'DNS records' (basic) category in the diagnosis for more info. If you recently modified your A record, please wait for it to propagate (some DNS propagation checkers are available online). (If you know what you are doing, use '--no-checks' to turn off those checks.)",
|
||||
"certmanager_domain_http_not_working": "Domain {domain:s} does not seem to be accessible through HTTP. Please check the 'Web' category in the diagnosis for more info. (If you know what you are doing, use '--no-checks' to turn off those checks.)",
|
||||
"certmanager_domain_unknown": "Unknown domain '{domain:s}'",
|
||||
"certmanager_warning_subdomain_dns_record": "Subdomain '{subdomain:s}' does not resolve to the same IP address as '{domain:s}'. Some features will not be available until you fix this and regenerate the certificate.",
|
||||
"certmanager_hit_rate_limit": "Too many certificates already issued for this exact set of domains {domain:s} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details",
|
||||
"certmanager_no_cert_file": "Could not read the certificate file for the domain {domain:s} (file: {file:s})",
|
||||
|
@ -274,8 +276,9 @@
|
|||
"domain_dyndns_already_subscribed": "You have already subscribed to a DynDNS domain",
|
||||
"domain_dyndns_root_unknown": "Unknown DynDNS root domain",
|
||||
"domain_exists": "The domain already exists",
|
||||
"domain_hostname_failed": "Could not set new hostname. This might cause an issue later (it might be fine).",
|
||||
"domain_hostname_failed": "Unable to set new hostname. This might cause an issue later (it might be fine).",
|
||||
"domain_uninstall_app_first": "Those applications are still installed on your domain: {apps}. Please uninstall them before proceeding to domain removal",
|
||||
"domain_name_unknown": "Domain '{domain}' unknown",
|
||||
"domain_unknown": "Unknown domain",
|
||||
"domains_available": "Available domains:",
|
||||
"done": "Done",
|
||||
|
@ -353,6 +356,7 @@
|
|||
"hook_name_unknown": "Unknown hook name '{name:s}'",
|
||||
"installation_complete": "Installation completed",
|
||||
"installation_failed": "Something went wrong with the installation",
|
||||
"invalid_regex": "Invalid regex:'{regex:s}'",
|
||||
"ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it",
|
||||
"iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it",
|
||||
"log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'",
|
||||
|
@ -410,16 +414,18 @@
|
|||
"mail_unavailable": "This e-mail address is reserved and shall be automatically allocated to the very first user",
|
||||
"main_domain_change_failed": "Unable to change the main domain",
|
||||
"main_domain_changed": "The main domain has been changed",
|
||||
"migrating_legacy_permission_settings": "Migrating legacy permission settings...",
|
||||
"migration_description_0015_migrate_to_buster": "Upgrade the system to Debian Buster and YunoHost 4.x",
|
||||
"migration_description_0016_php70_to_php73_pools": "Migrate php7.0-fpm 'pool' conf files to php7.3",
|
||||
"migration_description_0017_postgresql_9p6_to_11": "Migrate databases from PostgreSQL 9.6 to 11",
|
||||
"migration_description_0018_xtable_to_nftable": "Migrate old network traffic rules to the new nftable system",
|
||||
"migration_0011_create_group": "Creating a group for each user…",
|
||||
"migration_0011_LDAP_update_failed": "Could not update LDAP. Error: {error:s}",
|
||||
"migration_description_0019_extend_permissions_features": "Extend/rework the app permission management system",
|
||||
"migration_0011_create_group": "Creating a group for each user...",
|
||||
"migration_0011_LDAP_update_failed": "Unable to update LDAP. Error: {error:s}",
|
||||
"migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP...",
|
||||
"migration_0011_update_LDAP_database": "Updating LDAP database...",
|
||||
"migration_0011_update_LDAP_schema": "Updating LDAP schema...",
|
||||
"migration_0011_failed_to_remove_stale_object": "Could not remove stale object {dn}: {error}",
|
||||
"migration_0011_failed_to_remove_stale_object": "Unable to remove stale object {dn}: {error}",
|
||||
"migration_0015_start" : "Starting migration to Buster",
|
||||
"migration_0015_patching_sources_list": "Patching the sources.lists...",
|
||||
"migration_0015_main_upgrade": "Starting main upgrade...",
|
||||
|
@ -439,6 +445,12 @@
|
|||
"migration_0017_not_enough_space": "Make sufficient space available in {path} to run the migration.",
|
||||
"migration_0018_failed_to_migrate_iptables_rules": "Failed to migrate legacy iptables rules to nftables: {error}",
|
||||
"migration_0018_failed_to_reset_legacy_rules": "Failed to reset legacy iptables rules: {error}",
|
||||
"migration_0019_add_new_attributes_in_ldap": "Add new attributes for permissions in LDAP database",
|
||||
"migration_0019_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.",
|
||||
"migration_0019_can_not_backup_before_migration": "The backup of the system could not be completed before the migration failed. Error: {error:s}",
|
||||
"migration_0019_migration_failed_trying_to_rollback": "Could not migrate... trying to roll back the system.",
|
||||
"migration_0019_rollback_success": "System rolled back.",
|
||||
"migration_0019_slapd_config_will_be_overwritten": "It looks like you manually edited the slapd configuration. For this critical migration, YunoHost needs to force the update of the slapd configuration. The original files will be backuped in {conf_backup_folder}.",
|
||||
"migrations_already_ran": "Those migrations are already done: {ids}",
|
||||
"migrations_cant_reach_migration_file": "Could not access migrations files at the path '%s'",
|
||||
"migrations_dependencies_not_satisfied": "Run these migrations: '{dependencies_id}', before migration {id}.",
|
||||
|
@ -490,7 +502,7 @@
|
|||
"permission_not_found": "Permission '{permission:s}' not found",
|
||||
"permission_update_failed": "Could not update permission '{permission}': {error}",
|
||||
"permission_updated": "Permission '{permission:s}' updated",
|
||||
"permission_update_nothing_to_do": "No permissions to update",
|
||||
"permission_protected": "Permission {permission} is protected. You cannot add or remove the visitors group to/from this permission.",
|
||||
"permission_require_account": "Permission {permission} only makes sense for users having an account, and therefore cannot be enabled for visitors.",
|
||||
"port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections",
|
||||
"port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections",
|
||||
|
@ -510,6 +522,8 @@
|
|||
"regenconf_failed": "Could not regenerate the configuration for category(s): {categories}",
|
||||
"regenconf_pending_applying": "Applying pending configuration for category '{category}'...",
|
||||
"regenconf_need_to_explicitly_specify_ssh": "The ssh configuration has been manually modified, but you need to explicitly specify category 'ssh' with --force to actually apply the changes.",
|
||||
"regex_incompatible_with_tile": "/!\\ Packagers! Permission '{permission}' has show_tile set to 'true' and you therefore cannot define a regex URL as the main URL",
|
||||
"regex_with_only_domain": "You can't use a regex for domain, only for path",
|
||||
"restore_already_installed_app": "An app with the ID '{app:s}' is already installed",
|
||||
"restore_already_installed_apps": "The following apps can't be restored because they are already installed: {apps}",
|
||||
"restore_app_failed": "Could not restore {app:s}",
|
||||
|
@ -567,9 +581,11 @@
|
|||
"service_reloaded_or_restarted": "The service '{service:s}' was reloaded or restarted",
|
||||
"service_start_failed": "Could not start the service '{service:s}'\n\nRecent service logs:{logs:s}",
|
||||
"service_started": "Service '{service:s}' started",
|
||||
"service_stop_failed": "Could not stop the service '{service:s}'\n\nRecent service logs:{logs:s}",
|
||||
"service_stop_failed": "Unable to stop the service '{service:s}'\n\nRecent service logs:{logs:s}",
|
||||
"service_stopped": "Service '{service:s}' stopped",
|
||||
"service_unknown": "Unknown service '{service:s}'",
|
||||
"show_tile_cant_be_enabled_for_url_not_defined": "You cannot enable 'show_tile' right now, because you must first define an URL for the permission '{permission}'",
|
||||
"show_tile_cant_be_enabled_for_regex": "You cannot enable 'show_tile' right no, because the URL for the permission '{permission}' is a regex",
|
||||
"ssowat_conf_generated": "SSOwat configuration generated",
|
||||
"ssowat_conf_updated": "SSOwat configuration updated",
|
||||
"system_upgraded": "System upgraded",
|
||||
|
@ -586,9 +602,10 @@
|
|||
"tools_upgrade_special_packages_completed": "YunoHost package upgrade completed.\nPress [Enter] to get the command line back",
|
||||
"unbackup_app": "{app:s} will not be saved",
|
||||
"unexpected_error": "Something unexpected went wrong: {error}",
|
||||
"unknown_main_domain_path": "Unknown domain or path for '{app}'. You need to specify a domain and a path to be able to specify a URL for permission.",
|
||||
"unlimit": "No quota",
|
||||
"unrestore_app": "{app:s} will not be restored",
|
||||
"update_apt_cache_failed": "Could not to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}",
|
||||
"update_apt_cache_failed": "Unable to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}",
|
||||
"update_apt_cache_warning": "Something went wrong while updating the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}",
|
||||
"updating_apt_cache": "Fetching available upgrades for system packages...",
|
||||
"upgrade_complete": "Upgrade complete",
|
||||
|
|
|
@ -148,6 +148,8 @@ def app_info(app, full=False):
|
|||
"""
|
||||
Get info for a specific app
|
||||
"""
|
||||
from yunohost.permission import user_permission_list
|
||||
|
||||
if not _is_installed(app):
|
||||
raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id())
|
||||
|
||||
|
@ -175,6 +177,8 @@ def app_info(app, full=False):
|
|||
ret['supports_backup_restore'] = (os.path.exists(os.path.join(APPS_SETTING_PATH, app, "scripts", "backup")) and
|
||||
os.path.exists(os.path.join(APPS_SETTING_PATH, app, "scripts", "restore")))
|
||||
ret['supports_multi_instance'] = is_true(local_manifest.get("multi_instance", False))
|
||||
permissions = user_permission_list(full=True, absolute_urls=True)["permissions"]
|
||||
ret['permissions'] = {p: i for p, i in permissions.items() if p.startswith(app + ".") and (i["url"] or i['additional_urls'])}
|
||||
return ret
|
||||
|
||||
|
||||
|
@ -208,20 +212,35 @@ def _app_upgradable(app_infos):
|
|||
|
||||
def app_map(app=None, raw=False, user=None):
|
||||
"""
|
||||
List apps by domain
|
||||
Returns a map of url <-> app id such as :
|
||||
|
||||
Keyword argument:
|
||||
user -- Allowed app map for a user
|
||||
raw -- Return complete dict
|
||||
app -- Specific app to map
|
||||
{
|
||||
"domain.tld/foo": "foo__2",
|
||||
"domain.tld/mail: "rainloop",
|
||||
"other.tld/": "bar",
|
||||
"sub.other.tld/pwet": "pwet",
|
||||
}
|
||||
|
||||
When using "raw", the structure changes to :
|
||||
|
||||
{
|
||||
"domain.tld": {
|
||||
"/foo": {"label": "App foo", "id": "foo__2"},
|
||||
"/mail": {"label": "Rainloop", "id: "rainloop"},
|
||||
},
|
||||
"other.tld": {
|
||||
"/": {"label": "Bar", "id": "bar"},
|
||||
},
|
||||
"sub.other.tld": {
|
||||
"/pwet": {"label": "Pwet", "id": "pwet"}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
from yunohost.permission import user_permission_list
|
||||
|
||||
apps = []
|
||||
result = {}
|
||||
permissions = user_permission_list(full=True)["permissions"]
|
||||
|
||||
if app is not None:
|
||||
if not _is_installed(app):
|
||||
|
@ -230,6 +249,7 @@ def app_map(app=None, raw=False, user=None):
|
|||
else:
|
||||
apps = os.listdir(APPS_SETTING_PATH)
|
||||
|
||||
permissions = user_permission_list(full=True, absolute_urls=True)["permissions"]
|
||||
for app_id in apps:
|
||||
app_settings = _get_app_settings(app_id)
|
||||
if not app_settings:
|
||||
|
@ -252,75 +272,45 @@ def app_map(app=None, raw=False, user=None):
|
|||
if user not in main_perm["corresponding_users"]:
|
||||
continue
|
||||
|
||||
domain = app_settings['domain']
|
||||
path = app_settings['path'].rstrip('/')
|
||||
label = app_settings['label']
|
||||
this_app_perms = {p: i for p, i in permissions.items() if p.startswith(app_id + ".") and (i["url"] or i['additional_urls'])}
|
||||
|
||||
def _sanitized_absolute_url(perm_url):
|
||||
# Nominal case : url is relative to the app's path
|
||||
if perm_url.startswith("/"):
|
||||
perm_domain = domain
|
||||
perm_path = path + perm_url.rstrip("/")
|
||||
# Otherwise, the urls starts with a domain name, like domain.tld/foo/bar
|
||||
# We want perm_domain = domain.tld and perm_path = "/foo/bar"
|
||||
else:
|
||||
perm_domain, perm_path = perm_url.split("/", 1)
|
||||
perm_path = "/" + perm_path.rstrip("/")
|
||||
|
||||
# N.B. : having '/' instead of empty string is needed in app_map
|
||||
# but should *not* be done in app_ssowatconf (yeah :[)
|
||||
perm_path = perm_path if perm_path.strip() != "" else "/"
|
||||
|
||||
return perm_domain, perm_path
|
||||
|
||||
this_app_perms = {p: i for p, i in permissions.items() if p.startswith(app_id + ".") and i["url"]}
|
||||
for perm_name, perm_info in this_app_perms.items():
|
||||
# If we're building the map for a specific user, check the user
|
||||
# actually is allowed for this specific perm
|
||||
if user and user not in perm_info["corresponding_users"]:
|
||||
continue
|
||||
if perm_info["url"].startswith("re:"):
|
||||
# Here, we have an issue if the chosen url is a regex, because
|
||||
# the url we want to add to the dict is going to be turned into
|
||||
# a clickable link (or analyzed by other parts of yunohost
|
||||
# code...). To put it otherwise : in the current code of ssowat,
|
||||
# you can't give access a user to a regex.
|
||||
#
|
||||
# Instead, as drafted by Josue, we could rework the ssowat logic
|
||||
# about how routes and their permissions are defined. So for example,
|
||||
# have a dict of
|
||||
# { "/route1": ["visitors", "user1", "user2", ...], # Public route
|
||||
# "/route2_with_a_regex$": ["user1", "user2"], # Private route
|
||||
# "/route3": None, # Skipped route idk
|
||||
# }
|
||||
# then each time a user try to request and url, we only keep the
|
||||
# longest matching rule and check the user is allowed etc...
|
||||
#
|
||||
# The challenge with this is (beside actually implementing it)
|
||||
# is that it creates a whole new mechanism that ultimately
|
||||
# replace all the existing logic about
|
||||
# protected/unprotected/skipped uris and regexes and we gotta
|
||||
# handle / migrate all the legacy stuff somehow if we don't
|
||||
# want to end up with a total mess in the future idk
|
||||
logger.error("Permission %s can't be added to the SSOwat configuration because it doesn't support regexes so far..." % perm_name)
|
||||
continue
|
||||
|
||||
perm_domain, perm_path = _sanitized_absolute_url(perm_info["url"])
|
||||
if perm_name.endswith(".main"):
|
||||
perm_label = label
|
||||
else:
|
||||
# e.g. if perm_name is wordpress.admin, we want "Blog (Admin)" (where Blog is the label of this app)
|
||||
perm_label = "%s (%s)" % (label, perm_name.rsplit(".")[-1].replace("_", " ").title())
|
||||
perm_label = perm_info['label']
|
||||
perm_all_urls = [] + ([perm_info["url"]] if perm_info["url"] else []) + perm_info['additional_urls']
|
||||
|
||||
if raw:
|
||||
if domain not in result:
|
||||
result[perm_domain] = {}
|
||||
result[perm_domain][perm_path] = {
|
||||
'label': perm_label,
|
||||
'id': app_id
|
||||
}
|
||||
else:
|
||||
result[perm_domain + perm_path] = perm_label
|
||||
for url in perm_all_urls:
|
||||
|
||||
# Here, we decide to completely ignore regex-type urls ...
|
||||
# Because :
|
||||
# - displaying them in regular "yunohost app map" output creates
|
||||
# a pretty big mess when there are multiple regexes for the same
|
||||
# app ? (c.f. for example lufi)
|
||||
# - it doesn't really make sense when checking app conflicts to
|
||||
# compare regexes ? (Or it could in some cases but ugh ?)
|
||||
#
|
||||
if url.startswith("re:"):
|
||||
continue
|
||||
|
||||
if not raw:
|
||||
result[url] = perm_label
|
||||
else:
|
||||
if "/" in url:
|
||||
perm_domain, perm_path = url.split("/", 1)
|
||||
perm_path = '/' + perm_path
|
||||
else:
|
||||
perm_domain = url
|
||||
perm_path = "/"
|
||||
if perm_domain not in result:
|
||||
result[perm_domain] = {}
|
||||
result[perm_domain][perm_path] = {
|
||||
'label': perm_label,
|
||||
'id': app_id
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
|
@ -337,7 +327,6 @@ def app_change_url(operation_logger, app, domain, path):
|
|||
|
||||
"""
|
||||
from yunohost.hook import hook_exec, hook_callback
|
||||
from yunohost.domain import _normalize_domain_path, _get_conflicting_apps
|
||||
|
||||
installed = _is_installed(app)
|
||||
if not installed:
|
||||
|
@ -357,17 +346,7 @@ def app_change_url(operation_logger, app, domain, path):
|
|||
raise YunohostError("app_change_url_identical_domains", domain=domain, path=path)
|
||||
|
||||
# Check the url is available
|
||||
conflicts = _get_conflicting_apps(domain, path, ignore_app=app)
|
||||
if conflicts:
|
||||
apps = []
|
||||
for path, app_id, app_label in conflicts:
|
||||
apps.append(" * {domain:s}{path:s} → {app_label:s} ({app_id:s})".format(
|
||||
domain=domain,
|
||||
path=path,
|
||||
app_id=app_id,
|
||||
app_label=app_label,
|
||||
))
|
||||
raise YunohostError('app_location_unavailable', apps="\n".join(apps))
|
||||
_assert_no_conflicting_apps(domain, path, ignore_app=app)
|
||||
|
||||
manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app))
|
||||
|
||||
|
@ -676,7 +655,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
|
|||
|
||||
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
|
||||
from yunohost.log import OperationLogger
|
||||
from yunohost.permission import user_permission_list, permission_create, permission_url, permission_delete, permission_sync_to_user
|
||||
from yunohost.permission import user_permission_list, user_permission_info, user_permission_update, permission_create, permission_url, permission_delete, permission_sync_to_user
|
||||
from yunohost.regenconf import manually_modified_files
|
||||
|
||||
# Fetch or extract sources
|
||||
|
@ -745,6 +724,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
|
|||
raise YunohostError('app_id_invalid')
|
||||
|
||||
app_id = manifest['id']
|
||||
label = label if label else manifest['name']
|
||||
|
||||
# Check requirements
|
||||
_check_manifest_requirements(manifest, app_id)
|
||||
|
@ -814,7 +794,6 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
|
|||
# Set initial app settings
|
||||
app_settings = {
|
||||
'id': app_instance_name,
|
||||
'label': label if label else manifest['name'],
|
||||
'install_time': int(time.time()),
|
||||
'current_revision': manifest.get('remote', {}).get('revision', "?")
|
||||
}
|
||||
|
@ -837,7 +816,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
|
|||
|
||||
# Initialize the main permission for the app
|
||||
# After the install, if apps don't have a domain and path defined, the default url '/' is removed from the permission
|
||||
permission_create(app_instance_name+".main", url="/", allowed=["all_users"])
|
||||
permission_create(app_instance_name+".main", allowed=["all_users"], label=label, show_tile=False, protected=False)
|
||||
|
||||
# Execute the app install script
|
||||
install_failed = True
|
||||
|
@ -958,14 +937,14 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
|
|||
os.system('chown -R root: %s' % app_setting_path)
|
||||
os.system('chown -R admin: %s/scripts' % app_setting_path)
|
||||
|
||||
# If an app doesn't have at least a domain and a path, assume it's not a webapp and remove the default "/" permission
|
||||
# If the app haven't set the url of the main permission and domain and path is set set / as main url
|
||||
app_settings = _get_app_settings(app_instance_name)
|
||||
domain = app_settings.get('domain', None)
|
||||
path = app_settings.get('path', None)
|
||||
if not (domain and path):
|
||||
permission_url(app_instance_name + ".main", url=None, sync_perm=False)
|
||||
|
||||
_migrate_legacy_permissions(app_instance_name)
|
||||
if domain and path and user_permission_info(app_instance_name + '.main')['url'] is None:
|
||||
permission_url(app_instance_name + ".main", url='/', sync_perm=False)
|
||||
if domain and path:
|
||||
user_permission_update(app_instance_name + ".main", show_tile=True, sync_perm=False)
|
||||
|
||||
permission_sync_to_user()
|
||||
|
||||
|
@ -1019,34 +998,6 @@ def dump_app_log_extract_for_debugging(operation_logger):
|
|||
logger.info(line)
|
||||
|
||||
|
||||
def _migrate_legacy_permissions(app):
|
||||
|
||||
from yunohost.permission import user_permission_list, user_permission_update
|
||||
|
||||
# Check if app is apparently using the legacy permission management, defined by the presence of something like
|
||||
# ynh_app_setting_set on unprotected_uris (or yunohost app setting)
|
||||
install_script_path = os.path.join(APPS_SETTING_PATH, app, 'scripts/install')
|
||||
install_script_content = open(install_script_path, "r").read()
|
||||
if not re.search(r"(yunohost app setting|ynh_app_setting_set) .*(unprotected|skipped)_uris", install_script_content):
|
||||
return
|
||||
|
||||
app_settings = _get_app_settings(app)
|
||||
app_perm_currently_allowed = user_permission_list()["permissions"][app + ".main"]["allowed"]
|
||||
|
||||
settings_say_it_should_be_public = (app_settings.get("unprotected_uris", None) == "/"
|
||||
or app_settings.get("skipped_uris", None) == "/")
|
||||
|
||||
# If the current permission says app is protected, but there are legacy rules saying it should be public...
|
||||
if app_perm_currently_allowed == ["all_users"] and settings_say_it_should_be_public:
|
||||
# Make it public
|
||||
user_permission_update(app + ".main", add="visitors", sync_perm=False)
|
||||
|
||||
# If the current permission says app is public, but there are no setting saying it should be public...
|
||||
if app_perm_currently_allowed == ["visitors"] and not settings_say_it_should_be_public:
|
||||
# Make is private
|
||||
user_permission_update(app + ".main", remove="visitors", sync_perm=False)
|
||||
|
||||
|
||||
@is_unit_operation()
|
||||
def app_remove(operation_logger, app):
|
||||
"""
|
||||
|
@ -1212,7 +1163,7 @@ def app_makedefault(operation_logger, app, domain=None):
|
|||
domain = app_domain
|
||||
operation_logger.related_to.append(('domain', domain))
|
||||
elif domain not in domain_list()['domains']:
|
||||
raise YunohostError('domain_unknown')
|
||||
raise YunohostError('domain_name_unknown', domain=domain)
|
||||
|
||||
if '/' in app_map(raw=True)[domain]:
|
||||
raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain,
|
||||
|
@ -1253,31 +1204,109 @@ def app_setting(app, key, value=None, delete=False):
|
|||
"""
|
||||
app_settings = _get_app_settings(app) or {}
|
||||
|
||||
if value is None and not delete:
|
||||
try:
|
||||
return app_settings[key]
|
||||
except Exception as e:
|
||||
logger.debug("cannot get app setting '%s' for '%s' (%s)", key, app, e)
|
||||
return None
|
||||
#
|
||||
# Legacy permission setting management
|
||||
# (unprotected, protected, skipped_uri/regex)
|
||||
#
|
||||
|
||||
is_legacy_permission_setting = any(key.startswith(word+"_") for word in ["unprotected", "protected", "skipped"])
|
||||
|
||||
if is_legacy_permission_setting:
|
||||
|
||||
from permission import user_permission_list, user_permission_update, permission_create, permission_delete, permission_url
|
||||
permissions = user_permission_list(full=True)['permissions']
|
||||
permission_name = "%s.legacy_%s_uris" % (app, key.split('_')[0])
|
||||
permission = permissions.get(permission_name)
|
||||
|
||||
# GET
|
||||
if value is None and not delete:
|
||||
# FIXME FIXME FIXME : what about the main url ...?
|
||||
return ','.join(permission['additional_urls']) if permission else None
|
||||
|
||||
# DELETE
|
||||
if delete:
|
||||
# If 'is_public' setting still exists, we interpret this as
|
||||
# coming from a legacy app (because new apps shouldn't manage the
|
||||
# is_public state themselves anymore...)
|
||||
#
|
||||
# In that case, we interpret the request for "deleting
|
||||
# unprotected/skipped" setting as willing to make the app
|
||||
# private
|
||||
if 'is_public' in app_settings and 'visitors' in permissions[app + ".main"]['allowed']:
|
||||
if key.startswith('unprotected_') or key.startswith('skipped_'):
|
||||
user_permission_update(app + ".main", remove="visitors")
|
||||
|
||||
if permission:
|
||||
permission_delete(permission_name)
|
||||
|
||||
# SET
|
||||
else:
|
||||
logger.warning("/!\\ Packagers! This app is still using the skipped/protected/unprotected_uris/regex settings which are now obsolete and deprecated... Instead, you should use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to initialize the public/private access. Check out the documentation at the bottom of yunohost.org/groups_and_permissions to learn how to use the new permission mechanism.")
|
||||
|
||||
urls = value
|
||||
# If the request is about the root of the app (/), ( = the vast majority of cases)
|
||||
# we interpret this as a change for the main permission
|
||||
# (i.e. allowing/disallowing visitors)
|
||||
if urls == '/':
|
||||
if key.startswith("unprotected_") or key.startswith("skipped_"):
|
||||
permission_url(app + ".main", url='/', sync_perm=False)
|
||||
user_permission_update(app + ".main", add="visitors")
|
||||
else:
|
||||
user_permission_update(app + ".main", remove="visitors")
|
||||
else:
|
||||
|
||||
urls = urls.split(",")
|
||||
if key.endswith('_regex'):
|
||||
urls = ['re:' + url for url in urls]
|
||||
|
||||
if permission:
|
||||
# In case of new regex, save the urls, to add a new time in the additional_urls
|
||||
# In case of new urls, we do the same thing but inversed
|
||||
if key.endswith('_regex'):
|
||||
# List of urls to save
|
||||
current_urls_or_regex = [url for url in permission['additional_urls'] if not url.startswith('re:')]
|
||||
else:
|
||||
# List of regex to save
|
||||
current_urls_or_regex = [url for url in permission['additional_urls'] if url.startswith('re:')]
|
||||
|
||||
new_urls = urls + current_urls_or_regex
|
||||
# We need to clear urls because in the old setting the new setting override the old one and dont just add some urls
|
||||
permission_url(permission_name, clear_urls=True, sync_perm=False)
|
||||
permission_url(permission_name, add_url=new_urls)
|
||||
else:
|
||||
from utils.legacy import legacy_permission_label
|
||||
# Let's create a "special" permission for the legacy settings
|
||||
permission_create(permission=permission_name,
|
||||
# FIXME find a way to limit to only the user allowed to the main permission
|
||||
allowed=['all_users'] if key.startswith('protected_') else ['all_users', 'visitors'],
|
||||
url=None,
|
||||
additional_urls=urls,
|
||||
auth_header=not key.startswith('skipped_'),
|
||||
label=legacy_permission_label(app, key.split('_')[0]),
|
||||
show_tile=False,
|
||||
protected=True)
|
||||
|
||||
|
||||
#
|
||||
# Regular setting management
|
||||
#
|
||||
|
||||
# GET
|
||||
if value is None and not delete:
|
||||
return app_settings.get(key, None)
|
||||
|
||||
# DELETE
|
||||
if delete:
|
||||
if key in app_settings:
|
||||
del app_settings[key]
|
||||
|
||||
# SET
|
||||
else:
|
||||
# FIXME: Allow multiple values for some keys?
|
||||
if key in ['redirected_urls', 'redirected_regex']:
|
||||
value = yaml.load(value)
|
||||
if any(key.startswith(word+"_") for word in ["unprotected", "protected", "skipped"]):
|
||||
logger.warning("/!\\ Packagers! This app is still using the skipped/protected/unprotected_uris/regex settings which are now obsolete and deprecated... Instead, you should use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to initialize the public/private access. Check out the documentation at the bottom of yunohost.org/groups_and_permissions to learn how to use the new permission mechanism.")
|
||||
|
||||
app_settings[key] = value
|
||||
_set_app_settings(app, app_settings)
|
||||
|
||||
# Fucking legacy permission management.
|
||||
# We need this because app temporarily set the app as unprotected to configure it with curl...
|
||||
if key.startswith("unprotected_") or key.startswith("skipped_") and value == "/":
|
||||
from permission import user_permission_update
|
||||
user_permission_update(app + ".main", add="visitors")
|
||||
_set_app_settings(app, app_settings)
|
||||
|
||||
|
||||
def app_register_url(app, domain, path):
|
||||
|
@ -1290,10 +1319,6 @@ def app_register_url(app, domain, path):
|
|||
path -- The path to be registered (e.g. /coffee)
|
||||
"""
|
||||
|
||||
# This line can't be moved on top of file, otherwise it creates an infinite
|
||||
# loop of import with tools.py...
|
||||
from .domain import _get_conflicting_apps, _normalize_domain_path
|
||||
|
||||
domain, path = _normalize_domain_path(domain, path)
|
||||
|
||||
# We cannot change the url of an app already installed simply by changing
|
||||
|
@ -1305,18 +1330,7 @@ def app_register_url(app, domain, path):
|
|||
raise YunohostError('app_already_installed_cant_change_url')
|
||||
|
||||
# Check the url is available
|
||||
conflicts = _get_conflicting_apps(domain, path)
|
||||
if conflicts:
|
||||
apps = []
|
||||
for path, app_id, app_label in conflicts:
|
||||
apps.append(" * {domain:s}{path:s} → {app_label:s} ({app_id:s})".format(
|
||||
domain=domain,
|
||||
path=path,
|
||||
app_id=app_id,
|
||||
app_label=app_label,
|
||||
))
|
||||
|
||||
raise YunohostError('app_location_unavailable', apps="\n".join(apps))
|
||||
_assert_no_conflicting_apps(domain, path)
|
||||
|
||||
app_setting(app, 'domain', value=domain)
|
||||
app_setting(app, 'path', value=path)
|
||||
|
@ -1329,111 +1343,56 @@ def app_ssowatconf():
|
|||
|
||||
"""
|
||||
from yunohost.domain import domain_list, _get_maindomain
|
||||
from yunohost.user import user_list
|
||||
from yunohost.permission import user_permission_list
|
||||
|
||||
main_domain = _get_maindomain()
|
||||
domains = domain_list()['domains']
|
||||
all_permissions = user_permission_list(full=True)['permissions']
|
||||
all_permissions = user_permission_list(full=True, ignore_system_perms=True, absolute_urls=True)['permissions']
|
||||
|
||||
skipped_urls = []
|
||||
skipped_regex = []
|
||||
unprotected_urls = []
|
||||
unprotected_regex = []
|
||||
protected_urls = []
|
||||
protected_regex = []
|
||||
permissions = {
|
||||
'core_skipped': {
|
||||
"users": [],
|
||||
"label": "Core permissions - skipped",
|
||||
"show_tile": False,
|
||||
"auth_header": False,
|
||||
"public": True,
|
||||
"uris": \
|
||||
[domain + '/yunohost/admin' for domain in domains] + \
|
||||
[domain + '/yunohost/api' for domain in domains] + [
|
||||
"re:^[^/]*/%.well%-known/ynh%-diagnosis/.*$",
|
||||
"re:^[^/]*/%.well%-known/acme%-challenge/.*$",
|
||||
"re:^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$"
|
||||
]
|
||||
}
|
||||
}
|
||||
redirected_regex = {main_domain + '/yunohost[\/]?$': 'https://' + main_domain + '/yunohost/sso/'}
|
||||
redirected_urls = {}
|
||||
|
||||
def _get_setting(settings, name):
|
||||
s = settings.get(name, None)
|
||||
return s.split(',') if s else []
|
||||
|
||||
for app in _installed_apps():
|
||||
|
||||
app_settings = read_yaml(APPS_SETTING_PATH + app + '/settings.yml')
|
||||
|
||||
if 'domain' not in app_settings:
|
||||
continue
|
||||
if 'path' not in app_settings:
|
||||
continue
|
||||
|
||||
# This 'no_sso' settings sound redundant to not having $path defined ....
|
||||
# At least from what I can see, all apps using it don't have a path defined ...
|
||||
if 'no_sso' in app_settings:
|
||||
continue
|
||||
|
||||
domain = app_settings['domain']
|
||||
path = app_settings['path'].rstrip('/')
|
||||
|
||||
def _sanitized_absolute_url(perm_url):
|
||||
# Nominal case : url is relative to the app's path
|
||||
if perm_url.startswith("/"):
|
||||
perm_domain = domain
|
||||
perm_path = path + perm_url.rstrip("/")
|
||||
# Otherwise, the urls starts with a domain name, like domain.tld/foo/bar
|
||||
# We want perm_domain = domain.tld and perm_path = "/foo/bar"
|
||||
else:
|
||||
perm_domain, perm_path = perm_url.split("/", 1)
|
||||
perm_path = "/" + perm_path.rstrip("/")
|
||||
|
||||
return perm_domain + perm_path
|
||||
|
||||
# Skipped
|
||||
skipped_urls += [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'skipped_uris')]
|
||||
skipped_regex += _get_setting(app_settings, 'skipped_regex')
|
||||
|
||||
# Redirected
|
||||
redirected_urls.update(app_settings.get('redirected_urls', {}))
|
||||
redirected_regex.update(app_settings.get('redirected_regex', {}))
|
||||
|
||||
# Legacy permission system using (un)protected_uris and _regex managed in app settings...
|
||||
unprotected_urls += [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'unprotected_uris')]
|
||||
protected_urls += [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'protected_uris')]
|
||||
unprotected_regex += _get_setting(app_settings, 'unprotected_regex')
|
||||
protected_regex += _get_setting(app_settings, 'protected_regex')
|
||||
|
||||
# New permission system
|
||||
this_app_perms = {name: info for name, info in all_permissions.items() if name.startswith(app + ".")}
|
||||
for perm_name, perm_info in this_app_perms.items():
|
||||
|
||||
# Ignore permissions for which there's no url defined
|
||||
if not perm_info["url"]:
|
||||
continue
|
||||
|
||||
# FIXME : gotta handle regex-urls here... meh
|
||||
url = _sanitized_absolute_url(perm_info["url"])
|
||||
perm_info["url"] = url
|
||||
if "visitors" in perm_info["allowed"]:
|
||||
if url not in unprotected_urls and url not in skipped_urls:
|
||||
unprotected_urls.append(url)
|
||||
|
||||
# Legacy stuff : we remove now protected-urls that might have been declared as unprotected earlier...
|
||||
protected_urls = [u for u in protected_urls if u != url]
|
||||
else:
|
||||
if url not in protected_urls:
|
||||
protected_urls.append(url)
|
||||
|
||||
# Legacy stuff : we remove now unprotected-urls / skipped-urls that might have been declared as protected earlier...
|
||||
unprotected_urls = [u for u in unprotected_urls if u != url]
|
||||
skipped_urls = [u for u in skipped_urls if u != url]
|
||||
|
||||
for domain in domains:
|
||||
skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api'])
|
||||
|
||||
# Authorize ynh remote diagnosis, ACME challenge and mail autoconfig urls
|
||||
skipped_regex.append("^[^/]*/%.well%-known/ynh%-diagnosis/.*$")
|
||||
skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$")
|
||||
skipped_regex.append("^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$")
|
||||
|
||||
|
||||
permissions_per_url = {}
|
||||
# New permission system
|
||||
for perm_name, perm_info in all_permissions.items():
|
||||
# Ignore permissions for which there's no url defined
|
||||
if not perm_info["url"]:
|
||||
continue
|
||||
permissions_per_url[perm_info["url"]] = perm_info['corresponding_users']
|
||||
|
||||
uris = [] + ([perm_info['url']] if perm_info['url'] else []) + perm_info['additional_urls']
|
||||
|
||||
# Ignore permissions for which there's no url defined
|
||||
if not uris:
|
||||
continue
|
||||
|
||||
permissions[perm_name] = {
|
||||
"users": perm_info['corresponding_users'],
|
||||
"label": perm_info['label'],
|
||||
"show_tile": perm_info['show_tile'] and perm_info['url'] and (not perm_info["url"].startswith('re:')),
|
||||
"auth_header": perm_info['auth_header'],
|
||||
"public": "visitors" in perm_info["allowed"],
|
||||
"uris": uris
|
||||
}
|
||||
|
||||
conf_dict = {
|
||||
'portal_domain': main_domain,
|
||||
|
@ -1445,17 +1404,9 @@ def app_ssowatconf():
|
|||
'Email': 'mail'
|
||||
},
|
||||
'domains': domains,
|
||||
'skipped_urls': skipped_urls,
|
||||
'unprotected_urls': unprotected_urls,
|
||||
'protected_urls': protected_urls,
|
||||
'skipped_regex': skipped_regex,
|
||||
'unprotected_regex': unprotected_regex,
|
||||
'protected_regex': protected_regex,
|
||||
'redirected_urls': redirected_urls,
|
||||
'redirected_regex': redirected_regex,
|
||||
'users': {username: app_map(user=username)
|
||||
for username in user_list()['users'].keys()},
|
||||
'permissions': permissions_per_url,
|
||||
'permissions': permissions,
|
||||
}
|
||||
|
||||
with open('/etc/ssowat/conf.json', 'w+') as f:
|
||||
|
@ -1465,13 +1416,12 @@ def app_ssowatconf():
|
|||
|
||||
|
||||
def app_change_label(app, new_label):
|
||||
from permission import user_permission_update
|
||||
installed = _is_installed(app)
|
||||
if not installed:
|
||||
raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id())
|
||||
|
||||
app_setting(app, "label", value=new_label)
|
||||
|
||||
app_ssowatconf()
|
||||
logger.warning(m18n.n('app_label_deprecated'))
|
||||
user_permission_update(app + ".main", label=new_label)
|
||||
|
||||
|
||||
# actions todo list:
|
||||
|
@ -2674,8 +2624,6 @@ def _parse_args_in_yunohost_format(user_answers, argument_questions):
|
|||
|
||||
def _validate_and_normalize_webpath(manifest, args_dict, app_folder):
|
||||
|
||||
from yunohost.domain import _get_conflicting_apps, _normalize_domain_path
|
||||
|
||||
# If there's only one "domain" and "path", validate that domain/path
|
||||
# is an available url and normalize the path.
|
||||
|
||||
|
@ -2689,18 +2637,7 @@ def _validate_and_normalize_webpath(manifest, args_dict, app_folder):
|
|||
domain, path = _normalize_domain_path(domain, path)
|
||||
|
||||
# Check the url is available
|
||||
conflicts = _get_conflicting_apps(domain, path)
|
||||
if conflicts:
|
||||
apps = []
|
||||
for path, app_id, app_label in conflicts:
|
||||
apps.append(" * {domain:s}{path:s} → {app_label:s} ({app_id:s})".format(
|
||||
domain=domain,
|
||||
path=path,
|
||||
app_id=app_id,
|
||||
app_label=app_label,
|
||||
))
|
||||
|
||||
raise YunohostError('app_location_unavailable', apps="\n".join(apps))
|
||||
_assert_no_conflicting_apps(domain, path)
|
||||
|
||||
# (We save this normalized path so that the install script have a
|
||||
# standard path format to deal with no matter what the user inputted)
|
||||
|
@ -2723,8 +2660,83 @@ def _validate_and_normalize_webpath(manifest, args_dict, app_folder):
|
|||
and re.search(r"(ynh_webpath_register|yunohost app checkurl)", install_script_content):
|
||||
|
||||
domain = domain_args[0][1]
|
||||
if _get_conflicting_apps(domain, "/"):
|
||||
raise YunohostError('app_full_domain_unavailable', domain=domain)
|
||||
_assert_no_conflicting_apps(domain, "/", full_domain=True)
|
||||
|
||||
|
||||
def _normalize_domain_path(domain, path):
|
||||
|
||||
# We want url to be of the format :
|
||||
# some.domain.tld/foo
|
||||
|
||||
# Remove http/https prefix if it's there
|
||||
if domain.startswith("https://"):
|
||||
domain = domain[len("https://"):]
|
||||
elif domain.startswith("http://"):
|
||||
domain = domain[len("http://"):]
|
||||
|
||||
# Remove trailing slashes
|
||||
domain = domain.rstrip("/").lower()
|
||||
path = "/" + path.strip("/")
|
||||
|
||||
return domain, path
|
||||
|
||||
|
||||
def _get_conflicting_apps(domain, path, ignore_app=None):
|
||||
"""
|
||||
Return a list of all conflicting apps with a domain/path (it can be empty)
|
||||
|
||||
Keyword argument:
|
||||
domain -- The domain for the web path (e.g. your.domain.tld)
|
||||
path -- The path to check (e.g. /coffee)
|
||||
ignore_app -- An optional app id to ignore (c.f. the change_url usecase)
|
||||
"""
|
||||
|
||||
from yunohost.domain import domain_list
|
||||
|
||||
domain, path = _normalize_domain_path(domain, path)
|
||||
|
||||
# Abort if domain is unknown
|
||||
if domain not in domain_list()['domains']:
|
||||
raise YunohostError('domain_name_unknown', domain=domain)
|
||||
|
||||
# Fetch apps map
|
||||
apps_map = app_map(raw=True)
|
||||
|
||||
# Loop through all apps to check if path is taken by one of them
|
||||
conflicts = []
|
||||
if domain in apps_map:
|
||||
# Loop through apps
|
||||
for p, a in apps_map[domain].items():
|
||||
if a["id"] == ignore_app:
|
||||
continue
|
||||
if path == p:
|
||||
conflicts.append((p, a["id"], a["label"]))
|
||||
# We also don't want conflicts with other apps starting with
|
||||
# same name
|
||||
elif path.startswith(p) or p.startswith(path):
|
||||
conflicts.append((p, a["id"], a["label"]))
|
||||
|
||||
return conflicts
|
||||
|
||||
|
||||
def _assert_no_conflicting_apps(domain, path, ignore_app=None, full_domain=False):
|
||||
|
||||
conflicts = _get_conflicting_apps(domain, path, ignore_app)
|
||||
|
||||
if conflicts:
|
||||
apps = []
|
||||
for path, app_id, app_label in conflicts:
|
||||
apps.append(" * {domain:s}{path:s} → {app_label:s} ({app_id:s})".format(
|
||||
domain=domain,
|
||||
path=path,
|
||||
app_id=app_id,
|
||||
app_label=app_label,
|
||||
))
|
||||
|
||||
if full_domain:
|
||||
raise YunohostError('app_full_domain_unavailable', domain=domain)
|
||||
else:
|
||||
raise YunohostError('app_location_unavailable', apps="\n".join(apps))
|
||||
|
||||
|
||||
def _make_environment_dict(args_dict, prefix="APP_ARG_"):
|
||||
|
|
|
@ -1213,9 +1213,15 @@ class RestoreManager():
|
|||
|
||||
# Restore permission for the app which is installed
|
||||
for permission_name, permission_infos in old_apps_permission.items():
|
||||
app_name = permission_name.split(".")[0]
|
||||
app_name, perm_name = permission_name.split(".")
|
||||
if _is_installed(app_name):
|
||||
permission_create(permission_name, url=permission_infos["url"], allowed=permission_infos["allowed"], sync_perm=False)
|
||||
permission_create(permission_name, allowed=permission_infos["allowed"],
|
||||
url=permission_infos["url"],
|
||||
additional_urls=permission_infos['additional_urls'],
|
||||
auth_header=permission_infos['auth_header'],
|
||||
label=permission_infos['label'] if perm_name == "main" else permission_infos["sublabel"],
|
||||
show_tile=permission_infos['show_tile'],
|
||||
protected=permission_infos["protected"], sync_perm=False)
|
||||
|
||||
permission_sync_to_user()
|
||||
|
||||
|
@ -1251,6 +1257,7 @@ class RestoreManager():
|
|||
name should be already install)
|
||||
"""
|
||||
from yunohost.user import user_group_list
|
||||
from yunohost.app import app_setting
|
||||
from yunohost.permission import permission_create, permission_delete, user_permission_list, permission_sync_to_user
|
||||
|
||||
def copytree(src, dst, symlinks=False, ignore=None):
|
||||
|
@ -1333,7 +1340,16 @@ class RestoreManager():
|
|||
else:
|
||||
should_be_allowed = [g for g in permission_infos["allowed"] if g in existing_groups]
|
||||
|
||||
permission_create(permission_name, url=permission_infos.get("url", None), allowed=should_be_allowed, sync_perm=False)
|
||||
perm_name = permission_name.split(".")[1]
|
||||
permission_create(permission_name,
|
||||
allowed=should_be_allowed,
|
||||
url=permission_infos.get("url"),
|
||||
additional_urls=permission_infos.get("additional_urls"),
|
||||
auth_header=permission_infos.get("auth_header"),
|
||||
label=permission_infos.get('label') if perm_name == "main" else permission_infos.get("sublabel"),
|
||||
show_tile=permission_infos.get("show_tile", None),
|
||||
protected=permission_infos.get("protected", True),
|
||||
sync_perm=False)
|
||||
|
||||
permission_sync_to_user()
|
||||
|
||||
|
@ -1344,6 +1360,19 @@ class RestoreManager():
|
|||
from yunohost.utils.legacy import SetupGroupPermissions
|
||||
SetupGroupPermissions.migrate_app_permission(app=app_instance_name)
|
||||
|
||||
# Migrate old settings
|
||||
legacy_permission_settings = [
|
||||
"skipped_uris",
|
||||
"unprotected_uris",
|
||||
"protected_uris",
|
||||
"skipped_regex",
|
||||
"unprotected_regex",
|
||||
"protected_regex"
|
||||
]
|
||||
if any(app_setting(app_instance_name, setting) is not None for setting in legacy_permission_settings):
|
||||
from yunohost.utils.legacy import migrate_legacy_permission_settings
|
||||
migrate_legacy_permission_settings(app=app_instance_name)
|
||||
|
||||
# Prepare env. var. to pass to script
|
||||
env_dict = self._get_env_var(app_instance_name)
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ def certificate_status(domain_list, full=False):
|
|||
for domain in domain_list:
|
||||
# Is it in Yunohost domain list?
|
||||
if domain not in yunohost_domains_list:
|
||||
raise YunohostError('certmanager_domain_unknown', domain=domain)
|
||||
raise YunohostError('domain_name_unknown', domain=domain)
|
||||
|
||||
certificates = {}
|
||||
|
||||
|
@ -249,7 +249,7 @@ def _certificate_install_letsencrypt(domain_list, force=False, no_checks=False,
|
|||
for domain in domain_list:
|
||||
yunohost_domains_list = yunohost.domain.domain_list()['domains']
|
||||
if domain not in yunohost_domains_list:
|
||||
raise YunohostError('certmanager_domain_unknown', domain=domain)
|
||||
raise YunohostError('domain_name_unknown', domain=domain)
|
||||
|
||||
# Is it self-signed?
|
||||
status = _get_status(domain)
|
||||
|
@ -340,7 +340,7 @@ def certificate_renew(domain_list, force=False, no_checks=False, email=False, st
|
|||
|
||||
# Is it in Yunohost dmomain list?
|
||||
if domain not in yunohost.domain.domain_list()['domains']:
|
||||
raise YunohostError('certmanager_domain_unknown', domain=domain)
|
||||
raise YunohostError('domain_name_unknown', domain=domain)
|
||||
|
||||
status = _get_status(domain)
|
||||
|
||||
|
|
113
src/yunohost/data_migrations/0019_extend_permissions_features.py
Normal file
113
src/yunohost/data_migrations/0019_extend_permissions_features.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
import time
|
||||
import os
|
||||
|
||||
from moulinette import m18n
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
from yunohost.tools import Migration
|
||||
from yunohost.permission import user_permission_list
|
||||
from yunohost.utils.legacy import migrate_legacy_permission_settings
|
||||
|
||||
logger = getActionLogger('yunohost.migration')
|
||||
|
||||
|
||||
class MyMigration(Migration):
|
||||
"""
|
||||
Add protected attribute in LDAP permission
|
||||
"""
|
||||
|
||||
required = True
|
||||
|
||||
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)
|
||||
|
||||
def run(self):
|
||||
|
||||
# FIXME : what do we really want to do here ...
|
||||
# Imho we should just force-regen the conf in all case, and maybe
|
||||
# just display a warning if we detect that the conf was manually modified
|
||||
|
||||
# Backup LDAP and the apps settings before to do the migration
|
||||
logger.info(m18n.n("migration_0019_backup_before_migration"))
|
||||
try:
|
||||
backup_folder = "/home/yunohost.backup/premigration/" + time.strftime('%Y%m%d-%H%M%S', time.gmtime())
|
||||
os.makedirs(backup_folder, 0o750)
|
||||
os.system("systemctl stop slapd")
|
||||
os.system("cp -r --preserve /etc/ldap %s/ldap_config" % backup_folder)
|
||||
os.system("cp -r --preserve /var/lib/ldap %s/ldap_db" % backup_folder)
|
||||
os.system("cp -r --preserve /etc/yunohost/apps %s/apps_settings" % backup_folder)
|
||||
except Exception as e:
|
||||
raise YunohostError("migration_0019_can_not_backup_before_migration", error=e)
|
||||
finally:
|
||||
os.system("systemctl start slapd")
|
||||
|
||||
try:
|
||||
# Update LDAP database
|
||||
self.add_new_ldap_attributes()
|
||||
|
||||
# Migrate old settings
|
||||
migrate_legacy_permission_settings()
|
||||
|
||||
except Exception as e:
|
||||
logger.warn(m18n.n("migration_0019_migration_failed_trying_to_rollback"))
|
||||
os.system("systemctl stop slapd")
|
||||
os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config
|
||||
os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder)
|
||||
os.system("cp -r --preserve %s/ldap_db/. /var/lib/ldap/" % backup_folder)
|
||||
os.system("cp -r --preserve %s/apps_settings/. /etc/yunohost/apps/" % backup_folder)
|
||||
os.system("systemctl start slapd")
|
||||
os.system("rm -r " + backup_folder)
|
||||
logger.info(m18n.n("migration_0019_rollback_success"))
|
||||
raise
|
||||
else:
|
||||
os.system("rm -r " + backup_folder)
|
|
@ -31,7 +31,7 @@ from moulinette.core import MoulinetteError
|
|||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
from yunohost.app import app_ssowatconf, _installed_apps, _get_app_settings
|
||||
from yunohost.app import app_ssowatconf, _installed_apps, _get_app_settings, _get_conflicting_apps
|
||||
from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf
|
||||
from yunohost.utils.network import get_public_ip
|
||||
from yunohost.log import is_unit_operation
|
||||
|
@ -188,7 +188,7 @@ def domain_remove(operation_logger, domain, force=False):
|
|||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
|
||||
if not force and domain not in domain_list()['domains']:
|
||||
raise YunohostError('domain_unknown')
|
||||
raise YunohostError('domain_name_unknown', domain=domain)
|
||||
|
||||
# Check domain is not the main domain
|
||||
if domain == _get_maindomain():
|
||||
|
@ -313,7 +313,7 @@ def domain_main_domain(operation_logger, new_main_domain=None):
|
|||
|
||||
# Check domain exists
|
||||
if new_main_domain not in domain_list()['domains']:
|
||||
raise YunohostError('domain_unknown')
|
||||
raise YunohostError('domain_name_unknown', domain=new_main_domain)
|
||||
|
||||
operation_logger.related_to.append(('domain', new_main_domain))
|
||||
operation_logger.start()
|
||||
|
@ -368,46 +368,6 @@ def domain_cert_renew(domain_list, force=False, no_checks=False, email=False, st
|
|||
return yunohost.certificate.certificate_renew(domain_list, force, no_checks, email, staging)
|
||||
|
||||
|
||||
def _get_conflicting_apps(domain, path, ignore_app=None):
|
||||
"""
|
||||
Return a list of all conflicting apps with a domain/path (it can be empty)
|
||||
|
||||
Keyword argument:
|
||||
domain -- The domain for the web path (e.g. your.domain.tld)
|
||||
path -- The path to check (e.g. /coffee)
|
||||
ignore_app -- An optional app id to ignore (c.f. the change_url usecase)
|
||||
"""
|
||||
|
||||
domain, path = _normalize_domain_path(domain, path)
|
||||
|
||||
# Abort if domain is unknown
|
||||
if domain not in domain_list()['domains']:
|
||||
raise YunohostError('domain_unknown')
|
||||
|
||||
# This import cannot be put on top of file because it would create a
|
||||
# recursive import...
|
||||
from yunohost.app import app_map
|
||||
|
||||
# Fetch apps map
|
||||
apps_map = app_map(raw=True)
|
||||
|
||||
# Loop through all apps to check if path is taken by one of them
|
||||
conflicts = []
|
||||
if domain in apps_map:
|
||||
# Loop through apps
|
||||
for p, a in apps_map[domain].items():
|
||||
if a["id"] == ignore_app:
|
||||
continue
|
||||
if path == p:
|
||||
conflicts.append((p, a["id"], a["label"]))
|
||||
# We also don't want conflicts with other apps starting with
|
||||
# same name
|
||||
elif path.startswith(p) or p.startswith(path):
|
||||
conflicts.append((p, a["id"], a["label"]))
|
||||
|
||||
return conflicts
|
||||
|
||||
|
||||
def domain_url_available(domain, path):
|
||||
"""
|
||||
Check availability of a web path
|
||||
|
@ -431,24 +391,6 @@ def _set_maindomain(domain):
|
|||
f.write(domain)
|
||||
|
||||
|
||||
def _normalize_domain_path(domain, path):
|
||||
|
||||
# We want url to be of the format :
|
||||
# some.domain.tld/foo
|
||||
|
||||
# Remove http/https prefix if it's there
|
||||
if domain.startswith("https://"):
|
||||
domain = domain[len("https://"):]
|
||||
elif domain.startswith("http://"):
|
||||
domain = domain[len("http://"):]
|
||||
|
||||
# Remove trailing slashes
|
||||
domain = domain.rstrip("/").lower()
|
||||
path = "/" + path.strip("/")
|
||||
|
||||
return domain, path
|
||||
|
||||
|
||||
def _build_dns_conf(domain, ttl=3600, include_empty_AAAA_if_no_ipv6=False):
|
||||
"""
|
||||
Internal function that will returns a data structure containing the needed
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
Manage permissions
|
||||
"""
|
||||
|
||||
import re
|
||||
import copy
|
||||
import grp
|
||||
import random
|
||||
|
@ -44,35 +45,65 @@ SYSTEM_PERMS = ["mail", "xmpp", "sftp", "ssh"]
|
|||
#
|
||||
|
||||
|
||||
def user_permission_list(short=False, full=False, ignore_system_perms=False):
|
||||
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, app_list
|
||||
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'])
|
||||
["cn", 'groupPermission', 'inheritPermission',
|
||||
'URL', 'additionalUrls', 'authHeader', 'label', 'showTile', 'isProtected'])
|
||||
|
||||
# Parse / organize information to be outputed
|
||||
apps = [app["id"] for app in app_list()["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
|
||||
|
||||
permissions[name] = {}
|
||||
permissions[name]["allowed"] = [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])]
|
||||
app = name.split('.')[0]
|
||||
|
||||
perm = {}
|
||||
perm["allowed"] = [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])]
|
||||
|
||||
if full:
|
||||
permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])]
|
||||
permissions[name]["url"] = infos.get("URL", [None])[0]
|
||||
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 = permissions.keys()
|
||||
|
@ -81,14 +112,20 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False):
|
|||
|
||||
|
||||
@is_unit_operation()
|
||||
def user_permission_update(operation_logger, permission, add=None, remove=None, sync_perm=True):
|
||||
def user_permission_update(operation_logger, permission, add=None, remove=None,
|
||||
label=None, show_tile=None,
|
||||
protected=None, force=False, sync_perm=True):
|
||||
"""
|
||||
Allow or Disallow a user or group to a permission for a specific application
|
||||
|
||||
Keyword argument:
|
||||
permission -- Name of the permission (e.g. mail or or wordpress or wordpress.editors)
|
||||
add -- List of groups or usernames to add to this permission
|
||||
remove -- List of groups or usernames to remove from to this permission
|
||||
add -- (optional) List of groups or usernames to add to this permission
|
||||
remove -- (optional) List of groups or usernames to remove from to this 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
|
||||
force -- (optional) Give the possibility to add/remove access from the visitor group to a protected permission
|
||||
"""
|
||||
from yunohost.user import user_group_list
|
||||
|
||||
|
@ -96,15 +133,18 @@ def user_permission_update(operation_logger, permission, add=None, remove=None,
|
|||
if "." not in permission:
|
||||
permission = permission + ".main"
|
||||
|
||||
existing_permission = user_permission_info(permission)
|
||||
|
||||
# Refuse to add "visitors" to mail, xmpp ... they require an account to make sense.
|
||||
if add and "visitors" in add and permission.split(".")[0] in SYSTEM_PERMS:
|
||||
raise YunohostError('permission_require_account', permission=permission)
|
||||
|
||||
# Fetch currently allowed groups for this permission
|
||||
# Refuse to add "visitors" to protected permission
|
||||
if ((add and "visitors" in add and existing_permission["protected"]) or \
|
||||
(remove and "visitors" in remove and existing_permission["protected"])) and not force:
|
||||
raise YunohostError('permission_protected', permission=permission)
|
||||
|
||||
existing_permission = user_permission_list(full=True)["permissions"].get(permission, None)
|
||||
if existing_permission is None:
|
||||
raise YunohostError('permission_not_found', permission=permission)
|
||||
# Fetch currently allowed groups for this permission
|
||||
|
||||
current_allowed_groups = existing_permission["allowed"]
|
||||
operation_logger.related_to.append(('app', permission.split(".")[0]))
|
||||
|
@ -123,8 +163,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None,
|
|||
logger.warning(m18n.n('permission_already_allowed', permission=permission, group=group))
|
||||
else:
|
||||
operation_logger.related_to.append(('group', group))
|
||||
|
||||
new_allowed_groups += groups_to_add
|
||||
new_allowed_groups += [group]
|
||||
|
||||
if remove:
|
||||
groups_to_remove = [remove] if not isinstance(remove, list) else remove
|
||||
|
@ -145,16 +184,22 @@ def user_permission_update(operation_logger, permission, add=None, remove=None,
|
|||
if "visitors" not in new_allowed_groups or len(new_allowed_groups) >= 3:
|
||||
logger.warning(m18n.n("permission_currently_allowed_for_all_users"))
|
||||
|
||||
# Don't update LDAP if we update exactly the same values
|
||||
if set(new_allowed_groups) == set(current_allowed_groups):
|
||||
logger.warning(m18n.n("permission_already_up_to_date"))
|
||||
return existing_permission
|
||||
# Note that we can get this argument as string if we it come from the CLI
|
||||
if isinstance(show_tile, str):
|
||||
if show_tile.lower() == "true":
|
||||
show_tile = True
|
||||
else:
|
||||
show_tile = False
|
||||
|
||||
if existing_permission['url'] and existing_permission['url'].startswith('re:') and show_tile:
|
||||
logger.warning(m18n.n('regex_incompatible_with_tile', regex=existing_permission['url'], permission=permission))
|
||||
|
||||
# Commit the new allowed group list
|
||||
|
||||
operation_logger.start()
|
||||
|
||||
new_permission = _update_ldap_group_permission(permission=permission, allowed=new_allowed_groups, sync_perm=sync_perm)
|
||||
new_permission = _update_ldap_group_permission(permission=permission, allowed=new_allowed_groups,
|
||||
label=label, show_tile=show_tile,
|
||||
protected=protected, sync_perm=sync_perm)
|
||||
|
||||
logger.debug(m18n.n('permission_updated', permission=permission))
|
||||
|
||||
|
@ -176,9 +221,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True):
|
|||
|
||||
# Fetch existing permission
|
||||
|
||||
existing_permission = user_permission_list(full=True)["permissions"].get(permission, None)
|
||||
if existing_permission is None:
|
||||
raise YunohostError('permission_not_found', permission=permission)
|
||||
existing_permission = user_permission_info(permission)
|
||||
|
||||
if existing_permission["allowed"] == ["all_users"]:
|
||||
logger.warning(m18n.n("permission_already_up_to_date"))
|
||||
|
@ -227,14 +270,22 @@ def user_permission_info(permission):
|
|||
|
||||
|
||||
@is_unit_operation()
|
||||
def permission_create(operation_logger, permission, url=None, allowed=None, sync_perm=True):
|
||||
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)
|
||||
url -- (optional) URL for which access will be allowed/forbidden
|
||||
allowed -- (optional) A list of group/user to allow for the permission
|
||||
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:
|
||||
|
@ -269,15 +320,18 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync
|
|||
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 url:
|
||||
attr_dict['URL'] = url
|
||||
|
||||
if allowed is not None:
|
||||
if not isinstance(allowed, list):
|
||||
allowed = [allowed]
|
||||
|
@ -296,21 +350,33 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync
|
|||
except Exception as e:
|
||||
raise YunohostError('permission_creation_failed', permission=permission, error=e)
|
||||
|
||||
new_permission = _update_ldap_group_permission(permission=permission, allowed=allowed, sync_perm=sync_perm)
|
||||
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
|
||||
|
||||
|
||||
@is_unit_operation()
|
||||
def permission_url(operation_logger, permission, url=None, sync_perm=True):
|
||||
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
|
||||
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()
|
||||
|
||||
|
@ -318,18 +384,59 @@ def permission_url(operation_logger, permission, url=None, sync_perm=True):
|
|||
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_list(full=True)["permissions"].get(permission, None)
|
||||
if not existing_permission:
|
||||
raise YunohostError('permission_not_found', permission=permission)
|
||||
existing_permission = user_permission_info(permission)
|
||||
|
||||
# Compute new url list
|
||||
old_url = existing_permission["url"]
|
||||
show_tile = existing_permission['show_tile']
|
||||
|
||||
if old_url == url:
|
||||
logger.warning(m18n.n('permission_update_nothing_to_do'))
|
||||
return existing_permission
|
||||
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
|
||||
|
||||
|
@ -337,7 +444,10 @@ def permission_url(operation_logger, permission, url=None, sync_perm=True):
|
|||
operation_logger.start()
|
||||
|
||||
try:
|
||||
ldap.update('cn=%s,ou=permission' % permission, {'URL': [url]})
|
||||
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)
|
||||
|
||||
|
@ -345,7 +455,7 @@ def permission_url(operation_logger, permission, url=None, sync_perm=True):
|
|||
permission_sync_to_user()
|
||||
|
||||
logger.debug(m18n.n('permission_updated', permission=permission))
|
||||
return user_permission_list(full=True)["permissions"][permission]
|
||||
return user_permission_info(permission)
|
||||
|
||||
|
||||
@is_unit_operation()
|
||||
|
@ -369,9 +479,7 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True)
|
|||
|
||||
# Make sure this permission exists
|
||||
|
||||
existing_permission = user_permission_list(full=True)["permissions"].get(permission, None)
|
||||
if not existing_permission:
|
||||
raise YunohostError('permission_not_found', permission=permission)
|
||||
_ = user_permission_info(permission)
|
||||
|
||||
# Actually delete the permission
|
||||
|
||||
|
@ -434,12 +542,17 @@ def permission_sync_to_user():
|
|||
os.system('nscd --invalidate=group')
|
||||
|
||||
|
||||
def _update_ldap_group_permission(permission, allowed, sync_perm=True):
|
||||
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 -- A list of group/user to allow for the 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:
|
||||
|
@ -454,20 +567,35 @@ def _update_ldap_group_permission(permission, allowed, sync_perm=True):
|
|||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
# Fetch currently allowed groups for this permission
|
||||
existing_permission = user_permission_list(full=True)["permissions"][permission]
|
||||
existing_permission = user_permission_info(permission)
|
||||
|
||||
if allowed is None:
|
||||
return existing_permission
|
||||
update = {}
|
||||
|
||||
allowed = [allowed] if not isinstance(allowed, list) else allowed
|
||||
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]
|
||||
|
||||
# Guarantee uniqueness of values in allowed, which would otherwise make ldap.update angry.
|
||||
allowed = set(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,
|
||||
{'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in allowed]})
|
||||
ldap.update('cn=%s,ou=permission' % permission, update)
|
||||
except Exception as e:
|
||||
raise YunohostError('permission_update_failed', permission=permission, error=e)
|
||||
|
||||
|
@ -476,7 +604,7 @@ def _update_ldap_group_permission(permission, allowed, sync_perm=True):
|
|||
if sync_perm:
|
||||
permission_sync_to_user()
|
||||
|
||||
new_permission = user_permission_list(full=True)["permissions"][permission]
|
||||
new_permission = user_permission_info(permission)
|
||||
|
||||
# Trigger app callbacks
|
||||
|
||||
|
@ -501,3 +629,119 @@ def _update_ldap_group_permission(permission, allowed, sync_perm=True):
|
|||
hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission, ','.join(effectively_removed_group)])
|
||||
|
||||
return new_permission
|
||||
|
||||
|
||||
def _get_absolute_url(url, base_path):
|
||||
#
|
||||
# For example transform:
|
||||
# (/api, domain.tld/nextcloud) into domain.tld/nextcloud/api
|
||||
# (/api, domain.tld/nextcloud/) into domain.tld/nextcloud/api
|
||||
# (re:/foo.*, domain.tld/app) into re:domain\.tld/app/foo.*
|
||||
# (domain.tld/bar, domain.tld/app) into domain.tld/bar
|
||||
#
|
||||
base_path = base_path.rstrip("/")
|
||||
if url is None:
|
||||
return None
|
||||
if url.startswith('/'):
|
||||
return base_path + url.rstrip("/")
|
||||
if url.startswith('re:/'):
|
||||
return 're:' + base_path.replace('.', '\\.') + url[3:]
|
||||
else:
|
||||
return url
|
||||
|
||||
|
||||
def _validate_and_sanitize_permission_url(url, app_base_path, app):
|
||||
"""
|
||||
Check and normalize the urls passed for all permissions
|
||||
Also check that the Regex is valid
|
||||
|
||||
As documented in the 'ynh_permission_create' helper:
|
||||
|
||||
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
|
||||
domain.tld -> domain.tld
|
||||
|
||||
'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.domain import domain_list
|
||||
from yunohost.app import _assert_no_conflicting_apps
|
||||
|
||||
domains = domain_list()['domains']
|
||||
|
||||
#
|
||||
# Regexes
|
||||
#
|
||||
|
||||
def validate_regex(regex):
|
||||
if '%' in regex:
|
||||
logger.warning("/!\\ Packagers! You are probably using a lua regex. You should use a PCRE regex instead.")
|
||||
return
|
||||
|
||||
try:
|
||||
re.compile(regex)
|
||||
except Exception:
|
||||
raise YunohostError('invalid_regex', regex=regex)
|
||||
|
||||
if url.startswith('re:'):
|
||||
|
||||
# regex without domain
|
||||
|
||||
if url.startswith('re:/'):
|
||||
validate_regex(url[4:])
|
||||
return url
|
||||
|
||||
# regex with domain
|
||||
|
||||
if '/' not in url:
|
||||
raise YunohostError('regex_with_only_domain')
|
||||
domain, path = url[3:].split('/', 1)
|
||||
path = '/' + path
|
||||
|
||||
if domain.replace('%', '').replace('\\', '') not in domains:
|
||||
raise YunohostError('domain_name_unknown', domain=domain)
|
||||
|
||||
validate_regex(path)
|
||||
|
||||
return 're:' + domain + path
|
||||
|
||||
#
|
||||
# "Regular" URIs
|
||||
#
|
||||
|
||||
def split_domain_path(url):
|
||||
url = url.strip("/")
|
||||
(domain, path) = url.split('/', 1) if "/" in url else (url, "/")
|
||||
if path != "/":
|
||||
path = "/" + path
|
||||
return (domain, path)
|
||||
|
||||
# uris without domain
|
||||
if url.startswith('/'):
|
||||
# if url is for example /admin/
|
||||
# we want sanitized_url to be: /admin
|
||||
# and (domain, path) to be : (domain.tld, /app/admin)
|
||||
sanitized_url = "/" + url.strip("/")
|
||||
domain, path = split_domain_path(app_base_path)
|
||||
path = "/" + path.strip("/") + sanitized_url
|
||||
|
||||
# uris with domain
|
||||
else:
|
||||
# if url is for example domain.tld/wat/
|
||||
# we want sanitized_url to be: domain.tld/wat
|
||||
# and (domain, path) to be : (domain.tld, /wat)
|
||||
domain, path = split_domain_path(url)
|
||||
sanitized_url = domain + path
|
||||
|
||||
if domain not in domains:
|
||||
raise YunohostError('domain_name_unknown', domain=domain)
|
||||
|
||||
_assert_no_conflicting_apps(domain, path, ignore_app=app)
|
||||
|
||||
return sanitized_url
|
||||
|
|
|
@ -4,8 +4,9 @@ import os
|
|||
from conftest import get_test_apps_dir
|
||||
|
||||
from yunohost.utils.error import YunohostError
|
||||
from yunohost.app import app_install, app_remove
|
||||
from yunohost.domain import _get_maindomain, domain_url_available, _normalize_domain_path
|
||||
from yunohost.app import app_install, app_remove, _normalize_domain_path
|
||||
from yunohost.domain import _get_maindomain, domain_url_available
|
||||
from yunohost.permission import _validate_and_sanitize_permission_url
|
||||
|
||||
# Get main domain
|
||||
maindomain = _get_maindomain()
|
||||
|
@ -62,3 +63,54 @@ def test_registerurl_baddomain():
|
|||
with pytest.raises(YunohostError):
|
||||
app_install(os.path.join(get_test_apps_dir(), "register_url_app_ynh"),
|
||||
args="domain=%s&path=%s" % ("yolo.swag", "/urlregisterapp"), force=True)
|
||||
|
||||
|
||||
def test_normalize_permission_path():
|
||||
# Relative path
|
||||
assert _validate_and_sanitize_permission_url("/wiki/", maindomain + '/path', 'test_permission') == "/wiki"
|
||||
assert _validate_and_sanitize_permission_url("/", maindomain + '/path', 'test_permission') == "/"
|
||||
assert _validate_and_sanitize_permission_url("//salut/", maindomain + '/path', 'test_permission') == "/salut"
|
||||
|
||||
# Full path
|
||||
assert _validate_and_sanitize_permission_url(maindomain + "/hey/", maindomain + '/path', 'test_permission') == maindomain + "/hey"
|
||||
assert _validate_and_sanitize_permission_url(maindomain + "//", maindomain + '/path', 'test_permission') == maindomain + "/"
|
||||
assert _validate_and_sanitize_permission_url(maindomain + "/", maindomain + '/path', 'test_permission') == maindomain + "/"
|
||||
|
||||
# Relative Regex
|
||||
assert _validate_and_sanitize_permission_url("re:/yolo.*/", maindomain + '/path', 'test_permission') == "re:/yolo.*/"
|
||||
assert _validate_and_sanitize_permission_url("re:/y.*o(o+)[a-z]*/bo\1y", maindomain + '/path', 'test_permission') == "re:/y.*o(o+)[a-z]*/bo\1y"
|
||||
|
||||
# Full Regex
|
||||
assert _validate_and_sanitize_permission_url("re:" + maindomain + "/yolo.*/", maindomain + '/path', 'test_permission') == "re:" + maindomain + "/yolo.*/"
|
||||
assert _validate_and_sanitize_permission_url("re:" + maindomain + "/y.*o(o+)[a-z]*/bo\1y", maindomain + '/path', 'test_permission') == "re:" + maindomain + "/y.*o(o+)[a-z]*/bo\1y"
|
||||
|
||||
|
||||
def test_normalize_permission_path_with_bad_regex():
|
||||
# Relative Regex
|
||||
with pytest.raises(YunohostError):
|
||||
_validate_and_sanitize_permission_url("re:/yolo.*[1-7]^?/", maindomain + '/path', 'test_permission')
|
||||
with pytest.raises(YunohostError):
|
||||
_validate_and_sanitize_permission_url("re:/yolo.*[1-7](]/", maindomain + '/path', 'test_permission')
|
||||
|
||||
# Full Regex
|
||||
with pytest.raises(YunohostError):
|
||||
_validate_and_sanitize_permission_url("re:" + maindomain + "/yolo?+/", maindomain + '/path', 'test_permission')
|
||||
with pytest.raises(YunohostError):
|
||||
_validate_and_sanitize_permission_url("re:" + maindomain + "/yolo[1-9]**/", maindomain + '/path', 'test_permission')
|
||||
|
||||
|
||||
def test_normalize_permission_path_with_unknown_domain():
|
||||
with pytest.raises(YunohostError):
|
||||
_validate_and_sanitize_permission_url("shouldntexist.tld/hey", maindomain + '/path', 'test_permission')
|
||||
with pytest.raises(YunohostError):
|
||||
_validate_and_sanitize_permission_url("re:shouldntexist.tld/hey.*", maindomain + '/path', 'test_permission')
|
||||
|
||||
|
||||
def test_normalize_permission_path_conflicting_path():
|
||||
app_install("./tests/apps/register_url_app_ynh",
|
||||
args="domain=%s&path=%s" % (maindomain, "/url/registerapp"), force=True)
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
_validate_and_sanitize_permission_url("/registerapp", maindomain + '/url', 'test_permission')
|
||||
with pytest.raises(YunohostError):
|
||||
_validate_and_sanitize_permission_url(maindomain + "/url/registerapp", maindomain + '/path', 'test_permission')
|
||||
|
|
|
@ -8,8 +8,9 @@ from conftest import message, raiseYunohostError, get_test_apps_dir
|
|||
from yunohost.app import app_install, app_remove, app_ssowatconf
|
||||
from yunohost.app import _is_installed
|
||||
from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete, _recursive_umount
|
||||
from yunohost.domain import _get_maindomain
|
||||
from yunohost.user import user_permission_list, user_create, user_list, user_delete
|
||||
from yunohost.domain import _get_maindomain, domain_list, domain_add, domain_remove
|
||||
from yunohost.user import user_create, user_list, user_delete
|
||||
from yunohost.permission import user_permission_list
|
||||
from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps
|
||||
from yunohost.hook import CUSTOM_HOOK_FOLDER
|
||||
|
||||
|
@ -33,7 +34,7 @@ def setup_function(function):
|
|||
|
||||
assert len(backup_list()["archives"]) == 0
|
||||
|
||||
markers = [m.name for m in function.__dict__.get("pytestmark", [])]
|
||||
markers = {m.name: {'args':m.args, 'kwargs':m.kwargs} for m in function.__dict__.get("pytestmark",[])}
|
||||
|
||||
if "with_wordpress_archive_from_2p4" in markers:
|
||||
add_archive_wordpress_from_2p4()
|
||||
|
@ -67,6 +68,11 @@ def setup_function(function):
|
|||
"&admin=alice")
|
||||
assert app_is_installed("permissions_app")
|
||||
|
||||
if "with_custom_domain" in markers:
|
||||
domain = markers['with_custom_domain']['args'][0]
|
||||
if domain not in domain_list()['domains']:
|
||||
domain_add(domain)
|
||||
|
||||
|
||||
def teardown_function(function):
|
||||
|
||||
|
@ -76,7 +82,7 @@ def teardown_function(function):
|
|||
delete_all_backups()
|
||||
uninstall_test_apps_if_needed()
|
||||
|
||||
markers = [m.name for m in function.__dict__.get("pytestmark", [])]
|
||||
markers = {m.name: {'args':m.args, 'kwargs':m.kwargs} for m in function.__dict__.get("pytestmark",[])}
|
||||
|
||||
if "clean_opt_dir" in markers:
|
||||
shutil.rmtree("/opt/test_backup_output_directory")
|
||||
|
@ -84,6 +90,9 @@ def teardown_function(function):
|
|||
if "alice" in user_list()["users"]:
|
||||
user_delete("alice")
|
||||
|
||||
if "with_custom_domain" in markers:
|
||||
domain = markers['with_custom_domain']['args'][0]
|
||||
domain_remove(domain)
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def check_LDAP_db_integrity_call():
|
||||
|
@ -407,6 +416,7 @@ def test_backup_using_copy_method(mocker):
|
|||
#
|
||||
|
||||
@pytest.mark.with_wordpress_archive_from_2p4
|
||||
@pytest.mark.with_custom_domain("yolo.test")
|
||||
def test_restore_app_wordpress_from_Ynh2p4(mocker):
|
||||
|
||||
with message(mocker, "restore_complete"):
|
||||
|
@ -415,6 +425,7 @@ def test_restore_app_wordpress_from_Ynh2p4(mocker):
|
|||
|
||||
|
||||
@pytest.mark.with_wordpress_archive_from_2p4
|
||||
@pytest.mark.with_custom_domain("yolo.test")
|
||||
def test_restore_app_script_failure_handling(monkeypatch, mocker):
|
||||
|
||||
def custom_hook_exec(name, *args, **kwargs):
|
||||
|
@ -468,6 +479,7 @@ def test_restore_app_not_in_backup(mocker):
|
|||
|
||||
|
||||
@pytest.mark.with_wordpress_archive_from_2p4
|
||||
@pytest.mark.with_custom_domain("yolo.test")
|
||||
def test_restore_app_already_installed(mocker):
|
||||
|
||||
assert not _is_installed("wordpress")
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
import socket
|
||||
import requests
|
||||
import pytest
|
||||
import string
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
|
||||
from conftest import message, raiseYunohostError, get_test_apps_dir
|
||||
|
||||
from yunohost.app import app_install, app_remove, app_change_url, app_map, _installed_apps
|
||||
from yunohost.app import app_install, app_upgrade, app_remove, app_change_url, app_map, _installed_apps, APPS_SETTING_PATH, _set_app_settings, _get_app_settings
|
||||
from yunohost.user import user_list, user_create, user_delete, \
|
||||
user_group_list, user_group_delete
|
||||
from yunohost.permission import user_permission_update, user_permission_list, user_permission_reset, \
|
||||
permission_create, permission_delete, permission_url
|
||||
from yunohost.domain import _get_maindomain
|
||||
from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list
|
||||
|
||||
# Get main domain
|
||||
maindomain = ""
|
||||
other_domains = []
|
||||
dummy_password = "test123Ynh"
|
||||
|
||||
# Dirty patch of DNS resolution. Force the DNS to 127.0.0.1 address even if dnsmasq have the public address.
|
||||
|
@ -21,6 +25,43 @@ dummy_password = "test123Ynh"
|
|||
|
||||
prv_getaddrinfo = socket.getaddrinfo
|
||||
|
||||
def _permission_create_with_dummy_app(permission, allowed=None,
|
||||
url=None, additional_urls=None, auth_header=True,
|
||||
label=None, show_tile=False,
|
||||
protected=True, sync_perm=True,
|
||||
domain=None, path=None):
|
||||
app = permission.split('.')[0]
|
||||
if app not in _installed_apps():
|
||||
app_setting_path = os.path.join(APPS_SETTING_PATH, app)
|
||||
if not os.path.exists(app_setting_path):
|
||||
os.makedirs(app_setting_path)
|
||||
settings = {'id': app, 'dummy_permission_app': True}
|
||||
if domain:
|
||||
settings['domain'] = domain
|
||||
if path:
|
||||
settings['path'] = path
|
||||
_set_app_settings(app, settings)
|
||||
|
||||
with open(os.path.join(APPS_SETTING_PATH, app, 'manifest.json'), 'w') as f:
|
||||
json.dump({
|
||||
"name": app,
|
||||
"id": app,
|
||||
"description": {
|
||||
"en": "Dummy app to test permissions"
|
||||
}
|
||||
}, f)
|
||||
permission_create(permission=permission, allowed=allowed, url=url, additional_urls=additional_urls, auth_header=auth_header,
|
||||
label=label, show_tile=show_tile, protected=protected, sync_perm=sync_perm)
|
||||
|
||||
|
||||
def _clear_dummy_app_settings():
|
||||
# Clean dummy app settings
|
||||
for app in _installed_apps():
|
||||
if _get_app_settings(app).get('dummy_permission_app', False):
|
||||
app_setting_path = os.path.join(APPS_SETTING_PATH, app)
|
||||
if os.path.exists(app_setting_path):
|
||||
shutil.rmtree(app_setting_path)
|
||||
|
||||
|
||||
def clean_user_groups_permission():
|
||||
for u in user_list()['users']:
|
||||
|
@ -31,7 +72,7 @@ def clean_user_groups_permission():
|
|||
user_group_delete(g)
|
||||
|
||||
for p in user_permission_list()['permissions']:
|
||||
if any(p.startswith(name) for name in ["wiki", "blog", "site", "permissions_app"]):
|
||||
if any(p.startswith(name) for name in ["wiki", "blog", "site", "web", "permissions_app"]):
|
||||
permission_delete(p, force=True, sync_perm=False)
|
||||
socket.getaddrinfo = prv_getaddrinfo
|
||||
|
||||
|
@ -40,11 +81,22 @@ def setup_function(function):
|
|||
clean_user_groups_permission()
|
||||
|
||||
global maindomain
|
||||
global other_domains
|
||||
maindomain = _get_maindomain()
|
||||
|
||||
markers = {m.name: {'args':m.args, 'kwargs':m.kwargs} for m in function.__dict__.get("pytestmark",[])}
|
||||
|
||||
if "other_domains" in markers:
|
||||
other_domains = ["domain_%s.dev" % string.ascii_lowercase[number] for number in range(markers['other_domains']['kwargs']['number'])]
|
||||
for domain in other_domains:
|
||||
if domain not in domain_list()['domains']:
|
||||
domain_add(domain)
|
||||
|
||||
# Dirty patch of DNS resolution. Force the DNS to 127.0.0.1 address even if dnsmasq have the public address.
|
||||
# Mainly used for 'can_access_webpage' function
|
||||
dns_cache = {(maindomain, 443, 0, 1): [(2, 1, 6, '', ('127.0.0.1', 443))]}
|
||||
for domain in other_domains:
|
||||
dns_cache[(domain, 443, 0, 1)] = [(2, 1, 6, '', ('127.0.0.1', 443))]
|
||||
|
||||
def new_getaddrinfo(*args):
|
||||
try:
|
||||
|
@ -57,13 +109,25 @@ def setup_function(function):
|
|||
|
||||
user_create("alice", "Alice", "White", maindomain, dummy_password)
|
||||
user_create("bob", "Bob", "Snow", maindomain, dummy_password)
|
||||
permission_create("wiki.main", url="/", allowed=["all_users"] , sync_perm=False)
|
||||
permission_create("blog.main", allowed=["all_users"], sync_perm=False)
|
||||
user_permission_update("blog.main", remove="all_users", add="alice")
|
||||
|
||||
_permission_create_with_dummy_app(permission="wiki.main", url="/", additional_urls=['/whatever','/idontnow'], auth_header=False,
|
||||
label="Wiki", show_tile=True,
|
||||
allowed=["all_users"], protected=False, sync_perm=False,
|
||||
domain=maindomain, path='/wiki')
|
||||
_permission_create_with_dummy_app(permission="blog.main", url="/", auth_header=True,
|
||||
show_tile=False,
|
||||
protected=False, sync_perm=False,
|
||||
allowed=["alice"], domain=maindomain, path='/blog')
|
||||
_permission_create_with_dummy_app(permission="blog.api", allowed=["visitors"], protected=True, sync_perm=True)
|
||||
|
||||
def teardown_function(function):
|
||||
clean_user_groups_permission()
|
||||
global other_domains
|
||||
for domain in other_domains:
|
||||
domain_remove(domain)
|
||||
other_domains = []
|
||||
|
||||
_clear_dummy_app_settings()
|
||||
|
||||
try:
|
||||
app_remove("permissions_app")
|
||||
except:
|
||||
|
@ -74,6 +138,12 @@ def teardown_function(function):
|
|||
pass
|
||||
|
||||
|
||||
def teardown_module(module):
|
||||
global other_domains
|
||||
for domain in other_domains:
|
||||
domain_remove(domain)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def check_LDAP_db_integrity_call():
|
||||
check_LDAP_db_integrity()
|
||||
|
@ -222,15 +292,44 @@ def can_access_webpage(webpath, logged_as=None):
|
|||
def test_permission_list():
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
|
||||
assert "wiki.main" in res
|
||||
assert "blog.main" in res
|
||||
assert "mail.main" in res
|
||||
assert "xmpp.main" in res
|
||||
|
||||
assert "wiki.main" in res
|
||||
assert "blog.main" in res
|
||||
assert "blog.api" in res
|
||||
|
||||
assert res['wiki.main']['allowed'] == ["all_users"]
|
||||
assert res['blog.main']['allowed'] == ["alice"]
|
||||
assert res['blog.api']['allowed'] == ["visitors"]
|
||||
assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"])
|
||||
assert res['blog.main']['corresponding_users'] == ["alice"]
|
||||
assert res['blog.api']['corresponding_users'] == []
|
||||
assert res['wiki.main']['url'] == "/"
|
||||
assert res['blog.main']['url'] == "/"
|
||||
assert res['blog.api']['url'] == None
|
||||
assert set(res['wiki.main']['additional_urls']) == {'/whatever', '/idontnow'}
|
||||
assert res['wiki.main']['protected'] == False
|
||||
assert res['blog.main']['protected'] == False
|
||||
assert res['blog.api']['protected'] == True
|
||||
assert res['wiki.main']['label'] == "Wiki"
|
||||
assert res['blog.main']['label'] == "Blog"
|
||||
assert res['blog.api']['label'] == "Blog (api)"
|
||||
assert res['wiki.main']['show_tile'] == True
|
||||
assert res['blog.main']['show_tile'] == False
|
||||
assert res['blog.api']['show_tile'] == False
|
||||
assert res['wiki.main']['auth_header'] == False
|
||||
assert res['blog.main']['auth_header'] == True
|
||||
assert res['blog.api']['auth_header'] == True
|
||||
|
||||
res = user_permission_list(full=True, absolute_urls=True)['permissions']
|
||||
assert res['wiki.main']['url'] == maindomain + "/wiki"
|
||||
assert res['blog.main']['url'] == maindomain + "/blog"
|
||||
assert res['blog.api']['url'] == None
|
||||
assert set(res['wiki.main']['additional_urls']) == {maindomain + '/wiki/whatever', maindomain + '/wiki/idontnow'}
|
||||
assert res['blog.main']['additional_urls'] == []
|
||||
assert res['blog.api']['additional_urls'] == []
|
||||
|
||||
|
||||
#
|
||||
# Create - Remove functions
|
||||
|
@ -239,12 +338,13 @@ def test_permission_list():
|
|||
|
||||
def test_permission_create_main(mocker):
|
||||
with message(mocker, "permission_created", permission="site.main"):
|
||||
permission_create("site.main", allowed=["all_users"])
|
||||
permission_create("site.main", allowed=["all_users"], protected=False)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "site.main" in res
|
||||
assert res['site.main']['allowed'] == ["all_users"]
|
||||
assert set(res['site.main']['corresponding_users']) == set(["alice", "bob"])
|
||||
assert res['site.main']['protected'] == False
|
||||
|
||||
|
||||
def test_permission_create_extra(mocker):
|
||||
|
@ -256,6 +356,7 @@ def test_permission_create_extra(mocker):
|
|||
# all_users is only enabled by default on .main perms
|
||||
assert "all_users" not in res['site.test']['allowed']
|
||||
assert res['site.test']['corresponding_users'] == []
|
||||
assert res['site.test']['protected'] == False
|
||||
|
||||
|
||||
def test_permission_create_with_specific_user():
|
||||
|
@ -266,6 +367,80 @@ def test_permission_create_with_specific_user():
|
|||
assert res['site.test']['allowed'] == ["alice"]
|
||||
|
||||
|
||||
def test_permission_create_with_tile_management(mocker):
|
||||
with message(mocker, "permission_created", permission="site.main"):
|
||||
_permission_create_with_dummy_app("site.main", allowed=["all_users"],
|
||||
label="The Site", show_tile=False,
|
||||
domain=maindomain, path='/site')
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "site.main" in res
|
||||
assert res['site.main']['label'] == "The Site"
|
||||
assert res['site.main']['show_tile'] == False
|
||||
|
||||
def test_permission_create_with_tile_management_with_main_default_value(mocker):
|
||||
with message(mocker, "permission_created", permission="site.main"):
|
||||
_permission_create_with_dummy_app("site.main", allowed=["all_users"], show_tile=True, url="/",
|
||||
domain=maindomain, path='/site')
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "site.main" in res
|
||||
assert res['site.main']['label'] == "Site"
|
||||
assert res['site.main']['show_tile'] == True
|
||||
|
||||
def test_permission_create_with_tile_management_with_not_main_default_value(mocker):
|
||||
with message(mocker, "permission_created", permission="wiki.api"):
|
||||
_permission_create_with_dummy_app("wiki.api", allowed=["all_users"], show_tile=True, url="/",
|
||||
domain=maindomain, path='/site')
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "wiki.api" in res
|
||||
assert res['wiki.api']['label'] == "Wiki (api)"
|
||||
assert res['wiki.api']['show_tile'] == True
|
||||
|
||||
|
||||
def test_permission_create_with_urls_management_without_url(mocker):
|
||||
with message(mocker, "permission_created", permission="wiki.api"):
|
||||
_permission_create_with_dummy_app("wiki.api", allowed=["all_users"],
|
||||
domain=maindomain, path='/site')
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "wiki.api" in res
|
||||
assert res['wiki.api']['url'] == None
|
||||
assert res['wiki.api']['additional_urls'] == []
|
||||
assert res['wiki.api']['auth_header'] == True
|
||||
|
||||
|
||||
def test_permission_create_with_urls_management_simple_domain(mocker):
|
||||
with message(mocker, "permission_created", permission="site.main"):
|
||||
_permission_create_with_dummy_app("site.main", allowed=["all_users"],
|
||||
url="/", additional_urls=['/whatever','/idontnow'], auth_header=False,
|
||||
domain=maindomain, path='/site')
|
||||
|
||||
res = user_permission_list(full=True, absolute_urls=True)['permissions']
|
||||
assert "site.main" in res
|
||||
assert res['site.main']['url'] == maindomain + "/site"
|
||||
assert set(res['site.main']['additional_urls']) == {maindomain + "/site/whatever", maindomain + "/site/idontnow"}
|
||||
assert res['site.main']['auth_header'] == False
|
||||
|
||||
|
||||
@pytest.mark.other_domains(number=2)
|
||||
def test_permission_create_with_urls_management_multiple_domain(mocker):
|
||||
with message(mocker, "permission_created", permission="site.main"):
|
||||
_permission_create_with_dummy_app("site.main", allowed=["all_users"],
|
||||
url=maindomain + "/site/something",
|
||||
additional_urls=[other_domains[0] + "/blabla",
|
||||
other_domains[1] + "/ahh"],
|
||||
auth_header=True,
|
||||
domain=maindomain, path='/site')
|
||||
|
||||
res = user_permission_list(full=True, absolute_urls=True)['permissions']
|
||||
assert "site.main" in res
|
||||
assert res['site.main']['url'] == maindomain + "/site/something"
|
||||
assert set(res['site.main']['additional_urls']) == {other_domains[0] + "/blabla", other_domains[1] + "/ahh"}
|
||||
assert res['site.main']['auth_header'] == True
|
||||
|
||||
|
||||
def test_permission_delete(mocker):
|
||||
with message(mocker, "permission_deleted", permission="wiki.main"):
|
||||
permission_delete("wiki.main", force=True)
|
||||
|
@ -273,6 +448,12 @@ def test_permission_delete(mocker):
|
|||
res = user_permission_list()['permissions']
|
||||
assert "wiki.main" not in res
|
||||
|
||||
with message(mocker, "permission_deleted", permission="blog.api"):
|
||||
permission_delete("blog.api", force=False)
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert "blog.api" not in res
|
||||
|
||||
#
|
||||
# Error on create - remove function
|
||||
#
|
||||
|
@ -372,6 +553,48 @@ def test_permission_reset_idempotency():
|
|||
assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"])
|
||||
|
||||
|
||||
def test_permission_change_label(mocker):
|
||||
with message(mocker, "permission_updated", permission="wiki.main"):
|
||||
user_permission_update("wiki.main", label="New Wiki")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['label'] == "New Wiki"
|
||||
|
||||
|
||||
def test_permission_change_label_with_same_value(mocker):
|
||||
with message(mocker, "permission_updated", permission="wiki.main"):
|
||||
user_permission_update("wiki.main", label="Wiki")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['label'] == "Wiki"
|
||||
|
||||
|
||||
def test_permission_switch_show_tile(mocker):
|
||||
# Note that from the actionmap the value is passed as string, not as bool
|
||||
# Try with lowercase
|
||||
with message(mocker, "permission_updated", permission="wiki.main"):
|
||||
user_permission_update("wiki.main", show_tile="false")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['show_tile'] == False
|
||||
|
||||
# Try with uppercase
|
||||
with message(mocker, "permission_updated", permission="wiki.main"):
|
||||
user_permission_update("wiki.main", show_tile="TRUE")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['show_tile'] == True
|
||||
|
||||
|
||||
def test_permission_switch_show_tile_with_same_value(mocker):
|
||||
# Note that from the actionmap the value is passed as string, not as bool
|
||||
with message(mocker, "permission_updated", permission="wiki.main"):
|
||||
user_permission_update("wiki.main", show_tile="True")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['show_tile'] == True
|
||||
|
||||
|
||||
#
|
||||
# Error on update function
|
||||
#
|
||||
|
@ -391,8 +614,30 @@ def test_permission_update_permission_that_doesnt_exist(mocker):
|
|||
user_permission_update("doesnt.exist", add="alice")
|
||||
|
||||
|
||||
def test_permission_protected_update(mocker):
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['blog.api']['allowed'] == ["visitors"]
|
||||
|
||||
with raiseYunohostError(mocker, "permission_protected"):
|
||||
user_permission_update("blog.api", remove="visitors")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['blog.api']['allowed'] == ["visitors"]
|
||||
|
||||
user_permission_update("blog.api", remove="visitors", force=True)
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['blog.api']['allowed'] == []
|
||||
|
||||
with raiseYunohostError(mocker, "permission_protected"):
|
||||
user_permission_update("blog.api", add="visitors")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['blog.api']['allowed'] == []
|
||||
|
||||
|
||||
# Permission url management
|
||||
|
||||
|
||||
def test_permission_redefine_url():
|
||||
permission_url("blog.main", url="/pwet")
|
||||
|
||||
|
@ -401,19 +646,190 @@ def test_permission_redefine_url():
|
|||
|
||||
|
||||
def test_permission_remove_url():
|
||||
permission_url("blog.main", url=None)
|
||||
permission_url("blog.main", clear_urls=True)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res["blog.main"]["url"] is None
|
||||
|
||||
|
||||
def test_permission_main_url_regex():
|
||||
permission_url("blog.main", url="re:/[a-z]+reboy/.*")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res["blog.main"]["url"] == "re:/[a-z]+reboy/.*"
|
||||
|
||||
res = user_permission_list(full=True, absolute_urls=True)['permissions']
|
||||
assert res["blog.main"]["url"] == "re:%s/blog/[a-z]+reboy/.*" % maindomain.replace('.', '\.')
|
||||
|
||||
|
||||
def test_permission_main_url_bad_regex(mocker):
|
||||
with raiseYunohostError(mocker, "invalid_regex"):
|
||||
permission_url("blog.main", url="re:/[a-z]++reboy/.*")
|
||||
|
||||
|
||||
@pytest.mark.other_domains(number=1)
|
||||
def test_permission_add_additional_url():
|
||||
permission_url("wiki.main", add_url=[other_domains[0] + "/heyby", "/myhouse"])
|
||||
|
||||
res = user_permission_list(full=True, absolute_urls=True)['permissions']
|
||||
assert res['wiki.main']['url'] == maindomain + "/wiki"
|
||||
assert set(res['wiki.main']['additional_urls']) == {maindomain + '/wiki/whatever',
|
||||
maindomain + '/wiki/idontnow',
|
||||
other_domains[0] + "/heyby",
|
||||
maindomain + '/wiki/myhouse'}
|
||||
|
||||
|
||||
def test_permission_add_additional_regex():
|
||||
permission_url("blog.main", add_url=["re:/[a-z]+reboy/.*"])
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res["blog.main"]["additional_urls"] == ["re:/[a-z]+reboy/.*"]
|
||||
|
||||
res = user_permission_list(full=True, absolute_urls=True)['permissions']
|
||||
assert res["blog.main"]["additional_urls"] == ["re:%s/blog/[a-z]+reboy/.*" % maindomain.replace('.', '\.')]
|
||||
|
||||
|
||||
def test_permission_add_additional_bad_regex(mocker):
|
||||
with raiseYunohostError(mocker, "invalid_regex"):
|
||||
permission_url("blog.main", add_url=["re:/[a-z]++reboy/.*"])
|
||||
|
||||
|
||||
def test_permission_remove_additional_url():
|
||||
permission_url("wiki.main", remove_url=['/whatever'])
|
||||
|
||||
res = user_permission_list(full=True, absolute_urls=True)['permissions']
|
||||
assert res['wiki.main']['url'] == maindomain + "/wiki"
|
||||
assert res['wiki.main']['additional_urls'] == [maindomain + '/wiki/idontnow']
|
||||
|
||||
|
||||
def test_permssion_add_additional_url_already_exist():
|
||||
permission_url("wiki.main", add_url=['/whatever', "/myhouse"])
|
||||
permission_url("wiki.main", add_url=['/whatever'])
|
||||
|
||||
res = user_permission_list(full=True, absolute_urls=True)['permissions']
|
||||
assert res['wiki.main']['url'] == maindomain + "/wiki"
|
||||
assert set(res['wiki.main']['additional_urls']) == {maindomain + '/wiki/whatever',
|
||||
maindomain + '/wiki/idontnow',
|
||||
maindomain + '/wiki/myhouse'}
|
||||
|
||||
|
||||
def test_permission_remove_additional_url_dont_exist():
|
||||
permission_url("wiki.main", remove_url=['/shouldntexist', '/whatever'])
|
||||
permission_url("wiki.main", remove_url=['/shouldntexist'])
|
||||
|
||||
res = user_permission_list(full=True, absolute_urls=True)['permissions']
|
||||
assert res['wiki.main']['url'] == maindomain + "/wiki"
|
||||
assert res['wiki.main']['additional_urls'] == [maindomain + '/wiki/idontnow']
|
||||
|
||||
|
||||
def test_permission_clear_additional_url():
|
||||
permission_url("wiki.main", clear_urls=True)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['url'] == None
|
||||
assert res['wiki.main']['additional_urls'] == []
|
||||
|
||||
|
||||
def test_permission_switch_auth_header():
|
||||
permission_url("wiki.main", auth_header=True)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['auth_header'] == True
|
||||
|
||||
permission_url("wiki.main", auth_header=False)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['auth_header'] == False
|
||||
|
||||
|
||||
def test_permission_switch_auth_header_with_same_value():
|
||||
permission_url("wiki.main", auth_header=False)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['auth_header'] == False
|
||||
|
||||
|
||||
# Permission protected
|
||||
|
||||
def test_permission_switch_protected():
|
||||
user_permission_update("wiki.main", protected=True)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['protected'] == True
|
||||
|
||||
user_permission_update("wiki.main", protected=False)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['protected'] == False
|
||||
|
||||
|
||||
def test_permission_switch_protected_with_same_value():
|
||||
user_permission_update("wiki.main", protected=False)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['protected'] == False
|
||||
|
||||
|
||||
# Test SSOWAT conf generation
|
||||
|
||||
def test_ssowat_conf():
|
||||
with open("/etc/ssowat/conf.json") as f:
|
||||
res = json.load(f)
|
||||
|
||||
permissions = res['permissions']
|
||||
assert "wiki.main" in permissions
|
||||
assert "blog.main" in permissions
|
||||
assert "blog.api" not in permissions # blog.api has no url/additional url defined and therefore is not added to ssowat conf
|
||||
|
||||
assert set(permissions['wiki.main']['users']) == {'alice', 'bob'}
|
||||
assert permissions['blog.main']['users'] == ['alice']
|
||||
|
||||
assert permissions['wiki.main']['uris'][0] == maindomain + "/wiki"
|
||||
|
||||
assert set(permissions['wiki.main']['uris']) == {maindomain + "/wiki",
|
||||
maindomain + "/wiki/whatever",
|
||||
maindomain + "/wiki/idontnow"}
|
||||
assert permissions['blog.main']['uris'] == [maindomain + "/blog"]
|
||||
|
||||
assert permissions['wiki.main']['public'] == False
|
||||
assert permissions['blog.main']['public'] == False
|
||||
|
||||
assert permissions['wiki.main']['auth_header'] == False
|
||||
assert permissions['blog.main']['auth_header'] == True
|
||||
|
||||
assert permissions['wiki.main']['label'] == "Wiki"
|
||||
assert permissions['blog.main']['label'] == "Blog"
|
||||
|
||||
assert permissions['wiki.main']['show_tile'] == True
|
||||
assert permissions['blog.main']['show_tile'] == False
|
||||
|
||||
|
||||
def test_show_tile_cant_be_enabled():
|
||||
_permission_create_with_dummy_app(permission="site.main", auth_header=False,
|
||||
label="Site", show_tile=True,
|
||||
allowed=["all_users"], protected=False, sync_perm=False,
|
||||
domain=maindomain, path="/site")
|
||||
|
||||
_permission_create_with_dummy_app(permission="web.main", url="re:/[a-z]{3}/bla", auth_header=False,
|
||||
label="Web", show_tile=True,
|
||||
allowed=["all_users"], protected=False, sync_perm=True,
|
||||
domain=maindomain, path="/web")
|
||||
|
||||
permissions = user_permission_list(full=True)['permissions']
|
||||
|
||||
assert permissions['site.main']['show_tile'] == False
|
||||
assert permissions['web.main']['show_tile'] == False
|
||||
|
||||
|
||||
#
|
||||
# Application interaction
|
||||
#
|
||||
|
||||
|
||||
@pytest.mark.other_domains(number=1)
|
||||
def test_permission_app_install():
|
||||
app_install(os.path.join(get_test_apps_dir(), "permissions_app_ynh"),
|
||||
args="domain=%s&path=%s&is_public=0&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
|
||||
args="domain=%s&domain_2=%s&path=%s&is_public=0&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "permissions_app.main" in res
|
||||
|
@ -439,9 +855,10 @@ def test_permission_app_install():
|
|||
assert maindomain + "/urlpermissionapp" in app_map(user="bob").keys()
|
||||
|
||||
|
||||
@pytest.mark.other_domains(number=1)
|
||||
def test_permission_app_remove():
|
||||
app_install(os.path.join(get_test_apps_dir(), "permissions_app_ynh"),
|
||||
args="domain=%s&path=%s&is_public=0&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
|
||||
args="domain=%s&domain_2=%s&path=%s&is_public=0&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True)
|
||||
app_remove("permissions_app")
|
||||
|
||||
# Check all permissions for this app got deleted
|
||||
|
@ -449,9 +866,10 @@ def test_permission_app_remove():
|
|||
assert not any(p.startswith("permissions_app.") for p in res.keys())
|
||||
|
||||
|
||||
@pytest.mark.other_domains(number=1)
|
||||
def test_permission_app_change_url():
|
||||
app_install(os.path.join(get_test_apps_dir(), "permissions_app_ynh"),
|
||||
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
|
||||
args="domain=%s&domain_2=%s&path=%s&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True)
|
||||
|
||||
# FIXME : should rework this test to look for differences in the generated app map / app tiles ...
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
|
@ -467,10 +885,29 @@ def test_permission_app_change_url():
|
|||
assert res['permissions_app.dev']['url'] == "/dev"
|
||||
|
||||
|
||||
@pytest.mark.other_domains(number=1)
|
||||
def test_permission_protection_management_by_helper():
|
||||
app_install("./tests/apps/permissions_app_ynh",
|
||||
args="domain=%s&domain_2=%s&path=%s&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['permissions_app.main']['protected'] == False
|
||||
assert res['permissions_app.admin']['protected'] == True
|
||||
assert res['permissions_app.dev']['protected'] == False
|
||||
|
||||
app_upgrade(["permissions_app"], file="./tests/apps/permissions_app_ynh")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['permissions_app.main']['protected'] == False
|
||||
assert res['permissions_app.admin']['protected'] == False
|
||||
assert res['permissions_app.dev']['protected'] == True
|
||||
|
||||
|
||||
@pytest.mark.other_domains(number=1)
|
||||
def test_permission_app_propagation_on_ssowat():
|
||||
|
||||
app_install(os.path.join(get_test_apps_dir(), "permissions_app_ynh"),
|
||||
args="domain=%s&path=%s&is_public=1&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
|
||||
args="domain=%s&domain_2=%s&path=%s&is_public=1&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "visitors" in res['permissions_app.main']['allowed']
|
||||
|
@ -497,10 +934,11 @@ def test_permission_app_propagation_on_ssowat():
|
|||
assert not can_access_webpage(app_webroot + "/admin", logged_as="bob")
|
||||
|
||||
|
||||
@pytest.mark.other_domains(number=1)
|
||||
def test_permission_legacy_app_propagation_on_ssowat():
|
||||
|
||||
app_install(os.path.join(get_test_apps_dir(), "legacy_app_ynh"),
|
||||
args="domain=%s&path=%s" % (maindomain, "/legacy"), force=True)
|
||||
args="domain=%s&domain_2=%s&path=%s" % (maindomain, other_domains[0], "/legacy"), force=True)
|
||||
|
||||
# App is configured as public by default using the legacy unprotected_uri mechanics
|
||||
# It should automatically be migrated during the install
|
||||
|
|
|
@ -133,7 +133,7 @@ def test_create_user_already_exists(mocker):
|
|||
user_create("alice", "Alice", "White", maindomain, "test123Ynh")
|
||||
|
||||
def test_create_user_with_domain_that_doesnt_exists(mocker):
|
||||
with raiseYunohostError(mocker, "domain_unknown"):
|
||||
with raiseYunohostError(mocker, "domain_name_unknown"):
|
||||
user_create("alice", "Alice", "White", "doesnt.exists", "test123Ynh")
|
||||
|
||||
def test_update_user_with_mail_address_already_taken(mocker):
|
||||
|
|
|
@ -126,7 +126,7 @@ def user_create(operation_logger, username, firstname, lastname, domain, passwor
|
|||
|
||||
# Check that the domain exists
|
||||
if domain not in domain_list()['domains']:
|
||||
raise YunohostError('domain_unknown', domain)
|
||||
raise YunohostError('domain_name_unknown', domain=domain)
|
||||
|
||||
mail = username + '@' + domain
|
||||
ldap = _get_ldap_interface()
|
||||
|
@ -461,7 +461,7 @@ def user_info(username):
|
|||
|
||||
if service_status("dovecot")["status"] != "running":
|
||||
logger.warning(m18n.n('mailbox_used_space_dovecot_down'))
|
||||
elif username not in user_permission_list(full=True)["permissions"]["mail.main"]["corresponding_users"]:
|
||||
elif username not in user_permission_info("mail.main")["corresponding_users"]:
|
||||
logger.warning(m18n.n('mailbox_disabled', user=username))
|
||||
else:
|
||||
try:
|
||||
|
@ -768,13 +768,14 @@ def user_group_info(groupname):
|
|||
|
||||
def user_permission_list(short=False, full=False):
|
||||
import yunohost.permission
|
||||
return yunohost.permission.user_permission_list(short, full)
|
||||
return yunohost.permission.user_permission_list(short, full, absolute_urls=True)
|
||||
|
||||
|
||||
def user_permission_update(permission, add=None, remove=None, sync_perm=True):
|
||||
def user_permission_update(permission, add=None, remove=None, label=None, show_tile=None, sync_perm=True):
|
||||
import yunohost.permission
|
||||
return yunohost.permission.user_permission_update(permission,
|
||||
add=add, remove=remove,
|
||||
label=label, show_tile=show_tile,
|
||||
sync_perm=sync_perm)
|
||||
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ from moulinette.utils.log import getActionLogger
|
|||
from moulinette.utils.filesystem import read_yaml
|
||||
|
||||
from yunohost.user import user_list, user_group_create, user_group_update
|
||||
from yunohost.app import app_setting, _installed_apps
|
||||
from yunohost.permission import permission_create, user_permission_update, permission_sync_to_user
|
||||
from yunohost.app import app_setting, _installed_apps, _get_app_settings, _set_app_settings
|
||||
from yunohost.permission import permission_create, user_permission_list, user_permission_update, permission_sync_to_user
|
||||
|
||||
logger = getActionLogger('yunohost.legacy')
|
||||
|
||||
|
@ -103,7 +103,7 @@ class SetupGroupPermissions():
|
|||
allowed = [user for user in permission.split(',') if user in known_users]
|
||||
else:
|
||||
allowed = ["all_users"]
|
||||
permission_create(app + ".main", url=url, allowed=allowed, sync_perm=False)
|
||||
permission_create(app + ".main", url=url, allowed=allowed, protected=False, sync_perm=False)
|
||||
|
||||
app_setting(app, 'allowed_users', delete=True)
|
||||
|
||||
|
@ -112,3 +112,97 @@ class SetupGroupPermissions():
|
|||
user_permission_update(app + ".main", add="visitors", sync_perm=False)
|
||||
|
||||
permission_sync_to_user()
|
||||
|
||||
|
||||
LEGACY_PERMISSION_LABEL = {
|
||||
("nextcloud", "skipped"): "api", # .well-known
|
||||
("libreto", "skipped"): "pad access", # /[^/]+
|
||||
("leed", "skipped"): "api", # /action.php, for cron task ...
|
||||
("mailman", "protected"): "admin", # /admin
|
||||
("prettynoemiecms", "protected"): "admin", # /admin
|
||||
("etherpad_mypads", "skipped"): "admin", # /admin
|
||||
("baikal", "protected"): "admin", # /admin/
|
||||
("couchpotato", "unprotected"): "api", # /api
|
||||
("freshrss", "skipped"): "api", # /api/,
|
||||
("portainer", "skipped"): "api", # /api/webhooks/
|
||||
("jeedom", "unprotected"): "api", # /core/api/jeeApi.php
|
||||
("bozon", "protected"): "user interface", # /index.php
|
||||
("limesurvey", "protected"): "admin", # /index.php?r=admin,/index.php?r=plugins,/scripts
|
||||
("kanboard", "unprotected"): "api", # /jsonrpc.php
|
||||
("seafile", "unprotected"): "medias", # /media
|
||||
("ttrss", "skipped"): "api", # /public.php,/api,/opml.php?op=publish
|
||||
("libreerp", "protected"): "admin", # /web/database/manager
|
||||
("z-push", "skipped"): "api", # $domain/[Aa]uto[Dd]iscover/.*
|
||||
("radicale", "skipped"): "?", # $domain$path_url
|
||||
("jirafeau", "protected"): "user interface", # $domain$path_url/$","$domain$path_url/admin.php.*$
|
||||
("opensondage", "protected"): "admin", # $domain$path_url/admin/
|
||||
("lstu", "protected"): "user interface", # $domain$path_url/login$","$domain$path_url/logout$","$domain$path_url/api$","$domain$path_url/extensions$","$domain$path_url/stats$","$domain$path_url/d/.*$","$domain$path_url/a$","$domain$path_url/$
|
||||
("lutim", "protected"): "user interface", # $domain$path_url/stats/?$","$domain$path_url/manifest.webapp/?$","$domain$path_url/?$","$domain$path_url/[d-m]/.*$
|
||||
("lufi", "protected"): "user interface", # $domain$path_url/stats$","$domain$path_url/manifest.webapp$","$domain$path_url/$","$domain$path_url/d/.*$","$domain$path_url/m/.*$
|
||||
("gogs", "skipped"): "api", # $excaped_domain$excaped_path/[%w-.]*/[%w-.]*/git%-receive%-pack,$excaped_domain$excaped_path/[%w-.]*/[%w-.]*/git%-upload%-pack,$excaped_domain$excaped_path/[%w-.]*/[%w-.]*/info/refs
|
||||
}
|
||||
|
||||
|
||||
def legacy_permission_label(app, permission_type):
|
||||
return LEGACY_PERMISSION_LABEL.get((app, permission_type), "Legacy %s urls" % permission_type)
|
||||
|
||||
|
||||
def migrate_legacy_permission_settings(app=None):
|
||||
|
||||
logger.info(m18n.n("migrating_legacy_permission_settings"))
|
||||
apps = _installed_apps()
|
||||
|
||||
if app:
|
||||
if app not in apps:
|
||||
logger.error("Can't migrate permission for app %s because it ain't installed..." % app)
|
||||
apps = []
|
||||
else:
|
||||
apps = [app]
|
||||
|
||||
for app in apps:
|
||||
|
||||
settings = _get_app_settings(app) or {}
|
||||
if settings.get("label"):
|
||||
user_permission_update(app + ".main", label=settings["label"], sync_perm=False)
|
||||
del settings["label"]
|
||||
|
||||
def _setting(name):
|
||||
s = settings.get(name)
|
||||
return s.split(',') if s else []
|
||||
|
||||
skipped_urls = [uri for uri in _setting('skipped_uris') if uri != '/']
|
||||
skipped_urls += ['re:' + regex for regex in _setting('skipped_regex')]
|
||||
unprotected_urls = [uri for uri in _setting('unprotected_uris') if uri != '/']
|
||||
unprotected_urls += ['re:' + regex for regex in _setting('unprotected_regex')]
|
||||
protected_urls = [uri for uri in _setting('protected_uris') if uri != '/']
|
||||
protected_urls += ['re:' + regex for regex in _setting('protected_regex')]
|
||||
|
||||
if skipped_urls != []:
|
||||
permission_create(app + ".legacy_skipped_uris", additional_urls=skipped_urls,
|
||||
auth_header=False, label=legacy_permission_label(app, "skipped"),
|
||||
show_tile=False, allowed='visitors', protected=True, sync_perm=False)
|
||||
if unprotected_urls != []:
|
||||
permission_create(app + ".legacy_unprotected_uris", additional_urls=unprotected_urls,
|
||||
auth_header=True, label=legacy_permission_label(app, "unprotected"),
|
||||
show_tile=False, allowed='visitors', protected=True, sync_perm=False)
|
||||
if protected_urls != []:
|
||||
permission_create(app + ".legacy_protected_uris", additional_urls=protected_urls,
|
||||
auth_header=True, label=legacy_permission_label(app, "protected"),
|
||||
show_tile=False, allowed=user_permission_list()['permissions'][app + ".main"]['allowed'],
|
||||
protected=True, sync_perm=False)
|
||||
|
||||
legacy_permission_settings = [
|
||||
"skipped_uris",
|
||||
"unprotected_uris",
|
||||
"protected_uris",
|
||||
"skipped_regex",
|
||||
"unprotected_regex",
|
||||
"protected_regex"
|
||||
]
|
||||
for key in legacy_permission_settings:
|
||||
if key in settings:
|
||||
del settings[key]
|
||||
|
||||
_set_app_settings(app, settings)
|
||||
|
||||
permission_sync_to_user()
|
||||
|
|
Loading…
Add table
Reference in a new issue