mirror of
https://github.com/YunoHost/moulinette.git
synced 2024-09-03 20:06:31 +02:00
Merge branch 'stretch-unstable' into enh-python3
This commit is contained in:
commit
6e5b389e71
74 changed files with 7544 additions and 221 deletions
|
@ -1,5 +1,11 @@
|
||||||
language: python
|
language: python
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- ldap-utils
|
||||||
|
- slapd
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- python: 3.5
|
- python: 3.5
|
||||||
|
|
14
debian/changelog
vendored
14
debian/changelog
vendored
|
@ -1,3 +1,17 @@
|
||||||
|
moulinette (3.7.0.2) stable; urgency=low
|
||||||
|
|
||||||
|
Bumping version number for stable release
|
||||||
|
|
||||||
|
-- Kay0u <pierre@kayou.io> Thu, 26 Mar 2020 22:03:23 +0000
|
||||||
|
|
||||||
|
moulinette (3.7.0.1) testing; urgency=low
|
||||||
|
|
||||||
|
- [fix] Slapd may crash if we try to update the LDAP with no change (moulinette#231)
|
||||||
|
|
||||||
|
Thanks to all contributors (Josue) <3 !
|
||||||
|
|
||||||
|
-- Kay0u <pierre@kayou.io> Sun, 15 Mar 2020 16:09:25 +0000
|
||||||
|
|
||||||
moulinette (3.7.0) testing; urgency=low
|
moulinette (3.7.0) testing; urgency=low
|
||||||
|
|
||||||
# ~ Major stuff
|
# ~ Major stuff
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"file_not_exist": "الملف غير موجود : '{path}'",
|
"file_not_exist": "الملف غير موجود : '{path}'",
|
||||||
"folder_exists": "إنّ المجلد موجود من قبل : '{path}'",
|
"folder_exists": "إنّ المجلد موجود من قبل : '{path}'",
|
||||||
"folder_not_exist": "المجلد غير موجود",
|
"folder_not_exist": "المجلد غير موجود",
|
||||||
"instance_already_running": "هناك نسخة خادوم تشتغل مِن قبل",
|
"instance_already_running": "هناك بالفعل عملية YunoHost جارية. الرجاء الانتظار حتى ينتهي الأمر قبل تشغيل آخر.",
|
||||||
"invalid_argument": "المُعامِل غير صالح '{argument}': {error}",
|
"invalid_argument": "المُعامِل غير صالح '{argument}': {error}",
|
||||||
"invalid_password": "كلمة السر خاطئة",
|
"invalid_password": "كلمة السر خاطئة",
|
||||||
"invalid_usage": "إستعمال غير صالح، إستخدم --help لعرض المساعدة",
|
"invalid_usage": "إستعمال غير صالح، إستخدم --help لعرض المساعدة",
|
||||||
|
@ -52,5 +52,7 @@
|
||||||
"command_unknown": "الأمر '{command:s}' مجهول؟",
|
"command_unknown": "الأمر '{command:s}' مجهول؟",
|
||||||
"corrupted_yaml": "قراءة مُشوّهة لنسق yaml مِن {ressource:s} (السبب : {error:s})",
|
"corrupted_yaml": "قراءة مُشوّهة لنسق yaml مِن {ressource:s} (السبب : {error:s})",
|
||||||
"info": "معلومة:",
|
"info": "معلومة:",
|
||||||
"warn_the_user_about_waiting_lock_again": "جارٍ الانتظار…"
|
"warn_the_user_about_waiting_lock_again": "جارٍ الانتظار…",
|
||||||
|
"warn_the_user_that_lock_is_acquired": "لقد انتهى تنفيذ ذاك الأمر ، جارٍ إطلاق الأمر",
|
||||||
|
"warn_the_user_about_waiting_lock": "هناك أمر لـ YunoHost قيد التشغيل حاليا. في انتظار انتهاء تنفيذه قبل تشغيل التالي"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
{}
|
{
|
||||||
|
"logged_out": "প্রস্থান",
|
||||||
|
"password": "পাসওয়ার্ড"
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"file_not_exist": "El fitxer no existeix: '{path}'",
|
"file_not_exist": "El fitxer no existeix: '{path}'",
|
||||||
"folder_exists": "La carpeta ja existeix: '{path}'",
|
"folder_exists": "La carpeta ja existeix: '{path}'",
|
||||||
"folder_not_exist": "La carpeta no existeix",
|
"folder_not_exist": "La carpeta no existeix",
|
||||||
"instance_already_running": "Una instància ja s'està executant",
|
"instance_already_running": "Ja hi ha una operació de YunoHost en curs. Espereu a que s'acabi abans d'executar-ne una altra.",
|
||||||
"invalid_argument": "Argument invàlid '{argument}': {error}",
|
"invalid_argument": "Argument invàlid '{argument}': {error}",
|
||||||
"invalid_password": "Contrasenya invàlida",
|
"invalid_password": "Contrasenya invàlida",
|
||||||
"invalid_usage": "Utilització invàlida, utilitzeu --help per veure l'ajuda",
|
"invalid_usage": "Utilització invàlida, utilitzeu --help per veure l'ajuda",
|
||||||
|
@ -55,5 +55,6 @@
|
||||||
"corrupted_toml": "El fitxer TOML ha estat corromput en la lectura des de {ressource:s} (motiu: {error:s})",
|
"corrupted_toml": "El fitxer TOML ha estat corromput en la lectura des de {ressource:s} (motiu: {error:s})",
|
||||||
"warn_the_user_about_waiting_lock": "Hi ha una altra ordre de YunoHost en execució, s'executarà aquesta ordre un cop l'anterior hagi acabat",
|
"warn_the_user_about_waiting_lock": "Hi ha una altra ordre de YunoHost en execució, s'executarà aquesta ordre un cop l'anterior hagi acabat",
|
||||||
"warn_the_user_about_waiting_lock_again": "Encara en espera…",
|
"warn_the_user_about_waiting_lock_again": "Encara en espera…",
|
||||||
"warn_the_user_that_lock_is_acquired": "l'altra ordre tot just ha acabat, ara s'executarà aquesta ordre"
|
"warn_the_user_that_lock_is_acquired": "l'altra ordre tot just ha acabat, ara s'executarà aquesta ordre",
|
||||||
|
"invalid_token": "Testimoni no vàlid - torneu-vos a autenticar"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"argument_required": "{argument}是必须的",
|
"argument_required": "参数“{argument}”是必须的",
|
||||||
"authentication_profile_required": "必须验证配置文件{profile}",
|
"authentication_profile_required": "必须验证配置文件{profile}",
|
||||||
"authentication_required": "需要验证",
|
"authentication_required": "需要验证",
|
||||||
"authentication_required_long": "此操作需要验证",
|
"authentication_required_long": "此操作需要验证",
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
"ldap_operation_error": "LDAP操作时发生了错误",
|
"ldap_operation_error": "LDAP操作时发生了错误",
|
||||||
"ldap_server_down": "无法连接LDAP服务器",
|
"ldap_server_down": "无法连接LDAP服务器",
|
||||||
"logged_in": "登录成功",
|
"logged_in": "登录成功",
|
||||||
"logged_out": "注销成功",
|
"logged_out": "登出",
|
||||||
"not_logged_in": "您未登录",
|
"not_logged_in": "您未登录",
|
||||||
"operation_interrupted": "操作中断",
|
"operation_interrupted": "操作中断",
|
||||||
"password": "密码",
|
"password": "密码",
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
"ldap_operation_error": "Ein Fehler trat während der LDAP Abfrage auf",
|
"ldap_operation_error": "Ein Fehler trat während der LDAP Abfrage auf",
|
||||||
"ldap_server_down": "LDAP-Server nicht erreichbar",
|
"ldap_server_down": "LDAP-Server nicht erreichbar",
|
||||||
"logged_in": "Angemeldet",
|
"logged_in": "Angemeldet",
|
||||||
"logged_out": "Ausgeloggt",
|
"logged_out": "Abgemeldet",
|
||||||
"not_logged_in": "Du bist nicht angemeldet",
|
"not_logged_in": "Du bist nicht angemeldet",
|
||||||
"operation_interrupted": "Vorgang unterbrochen",
|
"operation_interrupted": "Vorgang unterbrochen",
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
{}
|
{
|
||||||
|
"logged_out": "Αποσυνδέθηκα",
|
||||||
|
"password": "Κωδικός πρόσβασης"
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
"invalid_token": "Invalid token - please authenticate",
|
"invalid_token": "Invalid token - please authenticate",
|
||||||
"invalid_usage": "Invalid usage, pass --help to see help",
|
"invalid_usage": "Invalid usage, pass --help to see help",
|
||||||
"ldap_attribute_already_exists": "Attribute '{attribute}' already exists with value '{value}'",
|
"ldap_attribute_already_exists": "Attribute '{attribute}' already exists with value '{value}'",
|
||||||
"ldap_operation_error": "An error occurred during LDAP operation",
|
"ldap_operation_error": "An error occurred during LDAP '{action}' operation",
|
||||||
"ldap_server_down": "Unable to reach LDAP server",
|
"ldap_server_down": "Unable to reach LDAP server",
|
||||||
"logged_in": "Logged in",
|
"logged_in": "Logged in",
|
||||||
"logged_out": "Logged out",
|
"logged_out": "Logged out",
|
||||||
|
@ -55,5 +55,5 @@
|
||||||
"command_unknown": "Command '{command:s}' unknown ?",
|
"command_unknown": "Command '{command:s}' unknown ?",
|
||||||
"warn_the_user_about_waiting_lock": "Another YunoHost command is running right now, we are waiting for it to finish before running this one",
|
"warn_the_user_about_waiting_lock": "Another YunoHost command is running right now, we are waiting for it to finish before running this one",
|
||||||
"warn_the_user_about_waiting_lock_again": "Still waiting...",
|
"warn_the_user_about_waiting_lock_again": "Still waiting...",
|
||||||
"warn_the_user_that_lock_is_acquired": "the other command just complet, now starting this command"
|
"warn_the_user_that_lock_is_acquired": "the other command just completed, now starting this command"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,59 @@
|
||||||
{
|
{
|
||||||
"password": "Pasvorto"
|
"password": "Pasvorto",
|
||||||
|
"colon": "{}: ",
|
||||||
|
"warn_the_user_that_lock_is_acquired": "la alia komando nur kompletigas, nun komencante ĉi tiun komandon",
|
||||||
|
"warn_the_user_about_waiting_lock_again": "Ankoraŭ atendanta...",
|
||||||
|
"warn_the_user_about_waiting_lock": "Alia komando de YunoHost funkcias ĝuste nun, ni atendas, ke ĝi finiĝos antaŭ ol funkcii ĉi tiu",
|
||||||
|
"command_unknown": "Komando '{command:s}' nekonata?",
|
||||||
|
"download_bad_status_code": "{url:s} redonita statuskodo {code:s}",
|
||||||
|
"download_unknown_error": "Eraro dum elŝutado de datumoj de {url:s}: {error:s}",
|
||||||
|
"download_timeout": "{url:s} prenis tro da tempo por respondi, rezignis.",
|
||||||
|
"download_ssl_error": "SSL-eraro dum konekto al {url:s}",
|
||||||
|
"invalid_url": "Nevalida url {url:s} (ĉu ĉi tiu retejo ekzistas?)",
|
||||||
|
"error_changing_file_permissions": "Eraro dum ŝanĝo de permesoj por {path:s}: {error:s}",
|
||||||
|
"error_removing": "Eraro dum la forigo de {path:s}: {error:s}",
|
||||||
|
"error_writing_file": "Eraro skribinte dosieron {file:s}: {error:s}",
|
||||||
|
"corrupted_toml": "Korupta toml legita el {ressource:s} (kialo: {error:s})",
|
||||||
|
"corrupted_yaml": "Korupta yaml legita de {ressource:s} (kialo: {error:s})",
|
||||||
|
"corrupted_json": "Koruptita json legita de {ressource:s} (kialo: {error:s})",
|
||||||
|
"unknown_error_reading_file": "Nekonata eraro dum provi legi dosieron {file:s} (kialo: {error:s})",
|
||||||
|
"cannot_write_file": "Ne povis skribi dosieron {file:s} (kialo: {error:s})",
|
||||||
|
"cannot_open_file": "Ne povis malfermi dosieron {file: s} (kialo: {error: s})",
|
||||||
|
"websocket_request_expected": "Atendis ret-peto",
|
||||||
|
"warning": "Averto:",
|
||||||
|
"values_mismatch": "Valoroj ne kongruas",
|
||||||
|
"unknown_user": "Nekonata uzanto '{user}'",
|
||||||
|
"unknown_group": "Nekonata grupo \"{group}\"",
|
||||||
|
"unable_retrieve_session": "Ne eblas retrovi la sesion ĉar '{exception}'",
|
||||||
|
"unable_authenticate": "Ne eblas aŭtentiĝi",
|
||||||
|
"success": "Sukceson!",
|
||||||
|
"server_already_running": "Servilo jam funkcias sur tiu haveno",
|
||||||
|
"root_required": "Vi devas esti 'root' por plenumi ĉi tiun agon",
|
||||||
|
"pattern_not_match": "Ne kongruas kun ŝablono",
|
||||||
|
"operation_interrupted": "Operacio interrompita",
|
||||||
|
"not_logged_in": "Vi ne estas ensalutinta",
|
||||||
|
"logged_in": "Ensalutinta",
|
||||||
|
"ldap_server_down": "Ne eblas atingi la servilon LDAP",
|
||||||
|
"ldap_operation_error": "Eraro okazis dum LDAP-operacio",
|
||||||
|
"ldap_attribute_already_exists": "Atributo '{attribute}' jam ekzistas kun valoro '{value}'",
|
||||||
|
"invalid_usage": "Nevalida uzado, preterpase '--help' por vidi helpon",
|
||||||
|
"invalid_password": "Nevalida pasvorto",
|
||||||
|
"invalid_argument": "Nevalida argumento '{argument}': {error}",
|
||||||
|
"instance_already_running": "Jam funkcias YunoHost-operacio. Bonvolu atendi, ke ĝi finiĝos antaŭ ol funkcii alia.",
|
||||||
|
"info": "informoj:",
|
||||||
|
"folder_not_exist": "Dosierujo ne ekzistas",
|
||||||
|
"folder_exists": "Dosierujo jam ekzistas: '{path}'",
|
||||||
|
"file_not_exist": "Dosiero ne ekzistas: '{path}'",
|
||||||
|
"file_exists": "Dosiero jam ekzistas: '{path}'",
|
||||||
|
"error_see_log": "Eraro okazis. Bonvolu vidi la protokolojn por detaloj, ili troviĝas en /var/log/yunohost/.",
|
||||||
|
"error": "Eraro:",
|
||||||
|
"deprecated_command_alias": "'{prog} {old}' malakceptas kaj estos forigita estonte, uzu anstataŭe '{prog} {new}'",
|
||||||
|
"deprecated_command": "'{prog} {command}' malakceptas kaj estos forigita estonte",
|
||||||
|
"confirm": "Konfirmu {prompt}",
|
||||||
|
"authentication_required_long": "Aŭtentigo necesas por plenumi ĉi tiun agon",
|
||||||
|
"authentication_required": "Aŭtentigo bezonata",
|
||||||
|
"authentication_profile_required": "Aŭtentigo al la profilo '{profile}' bezonata",
|
||||||
|
"argument_required": "Argumento '{argument}' estas bezonata",
|
||||||
|
"logged_out": "Ensalutinta",
|
||||||
|
"invalid_token": "Nevalida tokeno - bonvolu autentiki"
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,8 @@
|
||||||
"corrupted_yaml": "Lectura corrupta de yaml desde {ressource:s} (motivo: {error:s})",
|
"corrupted_yaml": "Lectura corrupta de yaml desde {ressource:s} (motivo: {error:s})",
|
||||||
"info": "Información:",
|
"info": "Información:",
|
||||||
"corrupted_toml": "Lectura corrupta de TOML desde {ressource:s} (motivo: {error:s})",
|
"corrupted_toml": "Lectura corrupta de TOML desde {ressource:s} (motivo: {error:s})",
|
||||||
"warn_the_user_that_lock_is_acquired": "la otra orden ha terminado, iniciando esta orden ahora",
|
"warn_the_user_that_lock_is_acquired": "la otra orden recién terminó, iniciando esta orden ahora",
|
||||||
"warn_the_user_about_waiting_lock_again": "Aún esperando...",
|
"warn_the_user_about_waiting_lock_again": "Aún esperando...",
|
||||||
"warn_the_user_about_waiting_lock": "Otra orden de YunoHost se está ejecutando ahora, estamos esperando a que termine antes de ejecutar esta"
|
"warn_the_user_about_waiting_lock": "Otra orden de YunoHost se está ejecutando ahora, estamos esperando a que termine antes de ejecutar esta",
|
||||||
|
"invalid_token": "Token invalido - vuelva a autenticarte"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
{
|
{
|
||||||
"argument_required": "'{argument}' argumentua beharrezkoa da"
|
"argument_required": "'{argument}' argumentua beharrezkoa da",
|
||||||
|
"logged_out": "Saioa amaitu",
|
||||||
|
"password": "Pasahitza",
|
||||||
|
"colon": "{}: "
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,5 +55,6 @@
|
||||||
"corrupted_toml": "Fichier TOML corrompu en lecture depuis {ressource:s} (cause : {error:s})",
|
"corrupted_toml": "Fichier TOML corrompu en lecture depuis {ressource:s} (cause : {error:s})",
|
||||||
"warn_the_user_about_waiting_lock": "Une autre commande YunoHost est actuellement en cours, nous attendons qu'elle se termine avant de démarrer celle là",
|
"warn_the_user_about_waiting_lock": "Une autre commande YunoHost est actuellement en cours, nous attendons qu'elle se termine avant de démarrer celle là",
|
||||||
"warn_the_user_about_waiting_lock_again": "Toujours en attente...",
|
"warn_the_user_about_waiting_lock_again": "Toujours en attente...",
|
||||||
"warn_the_user_that_lock_is_acquired": "l'autre commande vient de se terminer, lancement de cette commande"
|
"warn_the_user_that_lock_is_acquired": "l'autre commande vient de se terminer, lancement de cette commande",
|
||||||
|
"invalid_token": "Jeton non valide - veuillez vous authentifier"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
{}
|
{
|
||||||
|
"logged_out": "Kilépett",
|
||||||
|
"password": "Jelszó"
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"file_not_exist": "Il file non esiste: '{path}'",
|
"file_not_exist": "Il file non esiste: '{path}'",
|
||||||
"folder_exists": "La cartella esiste già: '{path}'",
|
"folder_exists": "La cartella esiste già: '{path}'",
|
||||||
"folder_not_exist": "La cartella non esiste",
|
"folder_not_exist": "La cartella non esiste",
|
||||||
"instance_already_running": "Un'istanza è già in esecuzione",
|
"instance_already_running": "Esiste già un'operazione YunoHost in esecuzione. Attendi il completamento prima di eseguirne un altro.",
|
||||||
"invalid_argument": "Argomento non valido '{argument}': {error}",
|
"invalid_argument": "Argomento non valido '{argument}': {error}",
|
||||||
"invalid_password": "Password non valida",
|
"invalid_password": "Password non valida",
|
||||||
"invalid_usage": "Utilizzo non valido, usa --help per vedere l'aiuto",
|
"invalid_usage": "Utilizzo non valido, usa --help per vedere l'aiuto",
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
"server_already_running": "Un server è già in esecuzione su quella porta",
|
"server_already_running": "Un server è già in esecuzione su quella porta",
|
||||||
"success": "Riuscito!",
|
"success": "Riuscito!",
|
||||||
"unable_authenticate": "Autenticazione fallita",
|
"unable_authenticate": "Autenticazione fallita",
|
||||||
"unable_retrieve_session": "Recupero della sessione non riuscito",
|
"unable_retrieve_session": "Impossibile recuperare la sessione perché \"{exception}\"",
|
||||||
"unknown_group": "Gruppo '{group}' sconosciuto",
|
"unknown_group": "Gruppo '{group}' sconosciuto",
|
||||||
"unknown_user": "Utente '{user}' sconosciuto",
|
"unknown_user": "Utente '{user}' sconosciuto",
|
||||||
"values_mismatch": "I valori non corrispondono",
|
"values_mismatch": "I valori non corrispondono",
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
"websocket_request_expected": "Richiesta WebSocket attesa",
|
"websocket_request_expected": "Richiesta WebSocket attesa",
|
||||||
"cannot_open_file": "Impossibile aprire il file {file:s} (motivo: {error:s})",
|
"cannot_open_file": "Impossibile aprire il file {file:s} (motivo: {error:s})",
|
||||||
"cannot_write_file": "Impossibile scrivere il file {file:s} (motivo: {error:s})",
|
"cannot_write_file": "Impossibile scrivere il file {file:s} (motivo: {error:s})",
|
||||||
"unknown_error_reading_file": "Errore sconosciuto nel tentativo di leggere il file {file:s}",
|
"unknown_error_reading_file": "Errore sconosciuto durante il tentativo di leggere il file {file:s} (motivo: {errore:s})",
|
||||||
"corrupted_json": "Lettura json corrotta da {ressource:s} (motivo: {error:s})",
|
"corrupted_json": "Lettura json corrotta da {ressource:s} (motivo: {error:s})",
|
||||||
"corrupted_yaml": "Lettura yaml corrotta da {ressource:s} (motivo: {error:s})",
|
"corrupted_yaml": "Lettura yaml corrotta da {ressource:s} (motivo: {error:s})",
|
||||||
"error_writing_file": "Errore durante la scrittura del file {file:s}: {error:s}",
|
"error_writing_file": "Errore durante la scrittura del file {file:s}: {error:s}",
|
||||||
|
@ -51,5 +51,10 @@
|
||||||
"download_unknown_error": "Errore durante il download di dati da {url:s} : {error:s}",
|
"download_unknown_error": "Errore durante il download di dati da {url:s} : {error:s}",
|
||||||
"download_bad_status_code": "{url:s} ha restituito il codice di stato {code:s}",
|
"download_bad_status_code": "{url:s} ha restituito il codice di stato {code:s}",
|
||||||
"command_unknown": "Comando '{command:s}' sconosciuto ?",
|
"command_unknown": "Comando '{command:s}' sconosciuto ?",
|
||||||
"info": "Info:"
|
"info": "Info:",
|
||||||
|
"warn_the_user_that_lock_is_acquired": "l'altro comando è appena completato, ora avvia questo comando",
|
||||||
|
"warn_the_user_about_waiting_lock_again": "Sto ancora aspettando ...",
|
||||||
|
"warn_the_user_about_waiting_lock": "Un altro comando YunoHost è in esecuzione in questo momento, stiamo aspettando che finisca prima di eseguire questo",
|
||||||
|
"corrupted_toml": "Toml corrotto da {ressource:s} (motivo: {errore:s})",
|
||||||
|
"invalid_token": "Token non valido: autenticare"
|
||||||
}
|
}
|
||||||
|
|
1
locales/ne.json
Normal file
1
locales/ne.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -11,7 +11,7 @@
|
||||||
"file_not_exist": "Bestand bestaat niet: '{path}'",
|
"file_not_exist": "Bestand bestaat niet: '{path}'",
|
||||||
"folder_exists": "Deze map bestaat al: '{path}'",
|
"folder_exists": "Deze map bestaat al: '{path}'",
|
||||||
"folder_not_exist": "Map bestaat niet",
|
"folder_not_exist": "Map bestaat niet",
|
||||||
"instance_already_running": "Er is al een instantie actief",
|
"instance_already_running": "Er is al een instantie actief, bedankt om te wachten tot deze afgesloten is alvorens een andere te starten.",
|
||||||
"invalid_argument": "Ongeldig argument '{argument}': {error}",
|
"invalid_argument": "Ongeldig argument '{argument}': {error}",
|
||||||
"invalid_password": "Ongeldig wachtwoord",
|
"invalid_password": "Ongeldig wachtwoord",
|
||||||
"invalid_usage": "Ongeldig gebruik, doe --help om de hulptekst te lezen",
|
"invalid_usage": "Ongeldig gebruik, doe --help om de hulptekst te lezen",
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
"server_already_running": "Er is al een server actief op die poort",
|
"server_already_running": "Er is al een server actief op die poort",
|
||||||
"success": "Succes!",
|
"success": "Succes!",
|
||||||
"unable_authenticate": "Aanmelding niet mogelijk",
|
"unable_authenticate": "Aanmelding niet mogelijk",
|
||||||
"unable_retrieve_session": "Kan de sessie niet ophalen",
|
"unable_retrieve_session": "Het is onmogelijk op de sessie op te halen omwille van '{exception}'",
|
||||||
"values_mismatch": "Waarden zijn niet gelijk",
|
"values_mismatch": "Waarden zijn niet gelijk",
|
||||||
"warning": "Waarschuwing:",
|
"warning": "Waarschuwing:",
|
||||||
"websocket_request_expected": "Verwachtte een WebSocket request",
|
"websocket_request_expected": "Verwachtte een WebSocket request",
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
"unknown_user": "Gebruiker '{user}' is onbekend",
|
"unknown_user": "Gebruiker '{user}' is onbekend",
|
||||||
"cannot_open_file": "Niet mogelijk om bestand {file:s} te openen (reden: {error:s})",
|
"cannot_open_file": "Niet mogelijk om bestand {file:s} te openen (reden: {error:s})",
|
||||||
"cannot_write_file": "Niet gelukt om bestand {file:s} te schrijven (reden: {error:s})",
|
"cannot_write_file": "Niet gelukt om bestand {file:s} te schrijven (reden: {error:s})",
|
||||||
"unknown_error_reading_file": "Ongekende fout tijdens het lezen van bestand {file:s}",
|
"unknown_error_reading_file": "Ongekende fout tijdens het lezen van bestand {file:s} (cause:{error:s})",
|
||||||
"corrupted_json": "Corrupte json gelezen van {ressource:s} (reden: {error:s})",
|
"corrupted_json": "Corrupte json gelezen van {ressource:s} (reden: {error:s})",
|
||||||
"error_writing_file": "Fout tijdens het schrijven van bestand {file:s}: {error:s}",
|
"error_writing_file": "Fout tijdens het schrijven van bestand {file:s}: {error:s}",
|
||||||
"error_removing": "Fout tijdens het verwijderen van {path:s}: {error:s}",
|
"error_removing": "Fout tijdens het verwijderen van {path:s}: {error:s}",
|
||||||
|
@ -49,5 +49,12 @@
|
||||||
"download_timeout": "{url:s} neemt te veel tijd om te antwoorden, we geven het op.",
|
"download_timeout": "{url:s} neemt te veel tijd om te antwoorden, we geven het op.",
|
||||||
"download_unknown_error": "Fout tijdens het downloaden van data van {url:s}: {error:s}",
|
"download_unknown_error": "Fout tijdens het downloaden van data van {url:s}: {error:s}",
|
||||||
"download_bad_status_code": "{url:s} stuurt status code {code:s}",
|
"download_bad_status_code": "{url:s} stuurt status code {code:s}",
|
||||||
"command_unknown": "Opdracht '{command:s}' ongekend ?"
|
"command_unknown": "Opdracht '{command:s}' ongekend ?",
|
||||||
|
"warn_the_user_that_lock_is_acquired": "het andere commando is net voltooid, starten van dit commando",
|
||||||
|
"warn_the_user_about_waiting_lock_again": "Nog steeds aan het wachten...",
|
||||||
|
"warn_the_user_about_waiting_lock": "Een ander YunoHost commando wordt uitgevoerd, we wachten tot het gedaan is alovrens dit te starten",
|
||||||
|
"corrupted_toml": "Ongeldige TOML werd gelezen op {ressource:s} (reason: {error:s})",
|
||||||
|
"corrupted_yaml": "Ongeldig YAML bestand op {ressource:s} (reason: {error:s})",
|
||||||
|
"invalid_token": "Ongeldig token - gelieve in te loggen",
|
||||||
|
"info": "Ter info:"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"file_not_exist": "Lo fichièr « {path} » existís pas",
|
"file_not_exist": "Lo fichièr « {path} » existís pas",
|
||||||
"folder_exists": "Lo repertòri existís ja : « {path} »",
|
"folder_exists": "Lo repertòri existís ja : « {path} »",
|
||||||
"folder_not_exist": "Lo repertòri existís pas",
|
"folder_not_exist": "Lo repertòri existís pas",
|
||||||
"instance_already_running": "Una instància es ja en execucion",
|
"instance_already_running": "I a ja una operacion de YunoHost en cors. Mercés d’esperar que s’acabe abans de ne lançar una mai.",
|
||||||
"invalid_argument": "Argument « {argument} » incorrècte : {error}",
|
"invalid_argument": "Argument « {argument} » incorrècte : {error}",
|
||||||
"invalid_password": "Senhal incorrècte",
|
"invalid_password": "Senhal incorrècte",
|
||||||
"ldap_server_down": "Impossible d’aténher lo servidor LDAP",
|
"ldap_server_down": "Impossible d’aténher lo servidor LDAP",
|
||||||
|
@ -55,5 +55,6 @@
|
||||||
"corrupted_toml": "Fichièr TOML corromput en lectura de {ressource:s} estant (rason : {error:s})",
|
"corrupted_toml": "Fichièr TOML corromput en lectura de {ressource:s} estant (rason : {error:s})",
|
||||||
"warn_the_user_about_waiting_lock": "Una autra comanda YunoHost es en execucion, sèm a esperar qu’acabe abans d’aviar aquesta d’aquí",
|
"warn_the_user_about_waiting_lock": "Una autra comanda YunoHost es en execucion, sèm a esperar qu’acabe abans d’aviar aquesta d’aquí",
|
||||||
"warn_the_user_about_waiting_lock_again": "Encara en espèra…",
|
"warn_the_user_about_waiting_lock_again": "Encara en espèra…",
|
||||||
"warn_the_user_that_lock_is_acquired": "l’autra comanda ven d’acabar, lançament d’aquesta comanda"
|
"warn_the_user_that_lock_is_acquired": "l’autra comanda ven d’acabar, ara lançament d’aquesta comanda",
|
||||||
|
"invalid_token": "Geton invalid - volgatz vos autentificar"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,58 @@
|
||||||
{}
|
{
|
||||||
|
"logged_out": "Wylogowano",
|
||||||
|
"password": "hasło",
|
||||||
|
"warn_the_user_that_lock_is_acquired": "drugie polecenie właśnie się zakończyło, teraz uruchamia to polecenie",
|
||||||
|
"warn_the_user_about_waiting_lock_again": "Wciąż czekam...",
|
||||||
|
"warn_the_user_about_waiting_lock": "Kolejne polecenie YunoHost jest teraz uruchomione, czekamy na jego zakończenie przed uruchomieniem tego",
|
||||||
|
"command_unknown": "Polecenie „{command:s}” jest nieznane?",
|
||||||
|
"download_bad_status_code": "{url:s} zwrócił kod stanu {code:s}",
|
||||||
|
"download_unknown_error": "Błąd podczas pobierania danych z {url:s}: {error:s}",
|
||||||
|
"download_timeout": "{url:s} odpowiedział zbyt długo, poddał się.",
|
||||||
|
"download_ssl_error": "Błąd SSL podczas łączenia z {url:s}",
|
||||||
|
"invalid_url": "Nieprawidłowy adres URL {url:s} (czy ta strona istnieje?)",
|
||||||
|
"error_changing_file_permissions": "Błąd podczas zmiany uprawnień dla {path:s}: {error:s}",
|
||||||
|
"error_removing": "Błąd podczas usuwania {path:s}: {error:s}",
|
||||||
|
"error_writing_file": "Błąd podczas zapisywania pliku {file:s}: {error:s}",
|
||||||
|
"corrupted_toml": "Uszkodzony toml z {ressource: s} (powód: {error:s})",
|
||||||
|
"corrupted_yaml": "Uszkodzony yaml odczytany z {ressource:s} (powód: {error:s})",
|
||||||
|
"corrupted_json": "Uszkodzony json odczytany z {ressource:s} (powód: {error:s})",
|
||||||
|
"unknown_error_reading_file": "Nieznany błąd podczas próby odczytania pliku {file:s} (przyczyna: {error:s})",
|
||||||
|
"cannot_write_file": "Nie można zapisać pliku {file:s} (przyczyna: {error:s})",
|
||||||
|
"cannot_open_file": "Nie można otworzyć pliku {file:s} (przyczyna: {error:s})",
|
||||||
|
"websocket_request_expected": "Oczekiwano żądania WebSocket",
|
||||||
|
"warning": "Ostrzeżenie:",
|
||||||
|
"values_mismatch": "Wartości nie pasują",
|
||||||
|
"unknown_user": "Nieznany użytkownik „{user}”",
|
||||||
|
"unknown_group": "Nieznana grupa „{group}”",
|
||||||
|
"unable_retrieve_session": "Nie można pobrać sesji, ponieważ „{exception}”",
|
||||||
|
"unable_authenticate": "Nie można uwierzytelnić",
|
||||||
|
"success": "Sukces!",
|
||||||
|
"server_already_running": "Serwer już działa na tym porcie",
|
||||||
|
"root_required": "Aby wykonać tę akcję, musisz być rootem",
|
||||||
|
"pattern_not_match": "Nie pasuje do wzoru",
|
||||||
|
"operation_interrupted": "Operacja przerwana",
|
||||||
|
"not_logged_in": "Nie jesteś zalogowany",
|
||||||
|
"logged_in": "Zalogowany",
|
||||||
|
"ldap_server_down": "Nie można połączyć się z serwerem LDAP",
|
||||||
|
"ldap_operation_error": "Wystąpił błąd podczas operacji LDAP",
|
||||||
|
"ldap_attribute_already_exists": "Atrybut „{attribute}” już istnieje z wartością „{value}”",
|
||||||
|
"invalid_usage": "Nieprawidłowe użycie. Przejdź --help, aby wyświetlić pomoc",
|
||||||
|
"invalid_token": "Nieprawidłowy token - proszę uwierzytelnić",
|
||||||
|
"invalid_password": "Nieprawidłowe hasło",
|
||||||
|
"invalid_argument": "Nieprawidłowy argument „{argument}”: {error}",
|
||||||
|
"instance_already_running": "Trwa już operacja YunoHost. Zaczekaj na zakończenie, zanim uruchomisz kolejny.",
|
||||||
|
"info": "Informacje:",
|
||||||
|
"folder_not_exist": "Folder nie istnieje",
|
||||||
|
"folder_exists": "Folder już istnieje: „{path}”",
|
||||||
|
"file_not_exist": "Plik nie istnieje: „{path}”",
|
||||||
|
"file_exists": "Plik już istnieje: „{path}”",
|
||||||
|
"error_see_log": "Wystąpił błąd. Szczegółowe informacje można znaleźć w dziennikach, znajdują się one w katalogu /var/log/yunohost/.",
|
||||||
|
"error": "Błąd:",
|
||||||
|
"deprecated_command_alias": "„{prog} {old}” jest przestarzałe i zostanie usunięte w przyszłości, zamiast tego użyj „{prog} {new}”",
|
||||||
|
"deprecated_command": "„{prog} {command}” jest przestarzałe i zostanie usunięte w przyszłości",
|
||||||
|
"confirm": "Potwierdź {prompt}",
|
||||||
|
"colon": "{}: ",
|
||||||
|
"authentication_required_long": "Do wykonania tej czynności wymagane jest uwierzytelnienie",
|
||||||
|
"authentication_required": "Wymagane uwierzytelnienie",
|
||||||
|
"argument_required": "Argument „{argument}” jest wymagany"
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"file_not_exist": "O ficheiro não existe: '{path}'",
|
"file_not_exist": "O ficheiro não existe: '{path}'",
|
||||||
"folder_exists": "A pasta já existe: '{path}'",
|
"folder_exists": "A pasta já existe: '{path}'",
|
||||||
"folder_not_exist": "A pasta não existe",
|
"folder_not_exist": "A pasta não existe",
|
||||||
"instance_already_running": "O serviço já está em execussão",
|
"instance_already_running": "Já existe uma operação YunoHost em execução. Aguarde o término antes de executar outro.",
|
||||||
"invalid_argument": "Argumento inválido '{argument}': {error}",
|
"invalid_argument": "Argumento inválido '{argument}': {error}",
|
||||||
"invalid_password": "Senha incorreta",
|
"invalid_password": "Senha incorreta",
|
||||||
"invalid_usage": "Uso invalido, utilizar --help para ver a ajuda",
|
"invalid_usage": "Uso invalido, utilizar --help para ver a ajuda",
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
"server_already_running": "Existe um servidor ativo nessa porta",
|
"server_already_running": "Existe um servidor ativo nessa porta",
|
||||||
"success": "Sucesso!",
|
"success": "Sucesso!",
|
||||||
"unable_authenticate": "Não foi possível autenticar",
|
"unable_authenticate": "Não foi possível autenticar",
|
||||||
"unable_retrieve_session": "Não foi possível recuperar a sessão",
|
"unable_retrieve_session": "Não foi possível recuperar a sessão porque '{exception}'",
|
||||||
"values_mismatch": "Os valores não coincidem",
|
"values_mismatch": "Os valores não coincidem",
|
||||||
"warning": "Aviso:",
|
"warning": "Aviso:",
|
||||||
"websocket_request_expected": "Esperado um pedido a WebSocket",
|
"websocket_request_expected": "Esperado um pedido a WebSocket",
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
"unknown_user": "Nome de utilizador '{user}' desconhecido",
|
"unknown_user": "Nome de utilizador '{user}' desconhecido",
|
||||||
"cannot_open_file": "Não foi possível abrir o arquivo {file:s} (reason: {error:s})",
|
"cannot_open_file": "Não foi possível abrir o arquivo {file:s} (reason: {error:s})",
|
||||||
"cannot_write_file": "Não foi possível abrir o arquivo {file:s} (reason: {error:s})",
|
"cannot_write_file": "Não foi possível abrir o arquivo {file:s} (reason: {error:s})",
|
||||||
"unknown_error_reading_file": "Erro desconhecido ao tentar ler o arquivo {file:s}",
|
"unknown_error_reading_file": "Erro desconhecido ao tentar ler o arquivo {file:s} (motivo: {error:s})",
|
||||||
"error_writing_file": "Erro ao gravar arquivo {file:s}: {error:s}",
|
"error_writing_file": "Erro ao gravar arquivo {file:s}: {error:s}",
|
||||||
"error_removing": "Erro ao remover {path:s}: {error:s}",
|
"error_removing": "Erro ao remover {path:s}: {error:s}",
|
||||||
"error_changing_file_permissions": "Erro ao alterar as permissões para {path:s}: {error:s}",
|
"error_changing_file_permissions": "Erro ao alterar as permissões para {path:s}: {error:s}",
|
||||||
|
@ -50,5 +50,11 @@
|
||||||
"download_bad_status_code": "{url:s} retornou o código de status {code:s}",
|
"download_bad_status_code": "{url:s} retornou o código de status {code:s}",
|
||||||
"command_unknown": "Comando '{command:s}' desconhecido ?",
|
"command_unknown": "Comando '{command:s}' desconhecido ?",
|
||||||
"corrupted_json": "Json corrompido lido do {ressource:s} (motivo: {error:s})",
|
"corrupted_json": "Json corrompido lido do {ressource:s} (motivo: {error:s})",
|
||||||
"corrupted_yaml": "Yaml corrompido lido do {ressource:s} (motivo: {error:s})"
|
"corrupted_yaml": "Yaml corrompido lido do {ressource:s} (motivo: {error:s})",
|
||||||
|
"warn_the_user_that_lock_is_acquired": "o outro comando acabou de concluir, agora iniciando este comando",
|
||||||
|
"warn_the_user_about_waiting_lock_again": "Ainda esperando...",
|
||||||
|
"warn_the_user_about_waiting_lock": "Outro comando YunoHost está sendo executado agora, estamos aguardando o término antes de executar este",
|
||||||
|
"corrupted_toml": "Toml corrompido lido em {ressource:s} (motivo: {error:s})",
|
||||||
|
"invalid_token": "Token inválido - autentique",
|
||||||
|
"info": "Informações:"
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
"websocket_request_expected": "Ожидается запрос WebSocket",
|
"websocket_request_expected": "Ожидается запрос WebSocket",
|
||||||
"cannot_open_file": "Не могу открыть файл {file:s} (причина: {error:s})",
|
"cannot_open_file": "Не могу открыть файл {file:s} (причина: {error:s})",
|
||||||
"cannot_write_file": "Не могу записать файл {file:s} (причина: {error:s})",
|
"cannot_write_file": "Не могу записать файл {file:s} (причина: {error:s})",
|
||||||
"unknown_error_reading_file": "Неизвестная ошибка при чтении файла {file:s}",
|
"unknown_error_reading_file": "Неизвестная ошибка при попытке прочитать файл {file:s} (причина: {error:s})",
|
||||||
"corrupted_yaml": "Повреждённой yaml получен от {ressource:s} (причина: {error:s})",
|
"corrupted_yaml": "Повреждённой yaml получен от {ressource:s} (причина: {error:s})",
|
||||||
"error_writing_file": "Ошибка при записи файла {file:s}: {error:s}",
|
"error_writing_file": "Ошибка при записи файла {file:s}: {error:s}",
|
||||||
"error_removing": "Ошибка при удалении {path:s}: {error:s}",
|
"error_removing": "Ошибка при удалении {path:s}: {error:s}",
|
||||||
|
@ -40,9 +40,20 @@
|
||||||
"download_ssl_error": "Ошибка SSL при соединении с {url:s}",
|
"download_ssl_error": "Ошибка SSL при соединении с {url:s}",
|
||||||
"download_timeout": "Превышено время ожидания ответа от {url:s}.",
|
"download_timeout": "Превышено время ожидания ответа от {url:s}.",
|
||||||
"download_unknown_error": "Ошибка при загрузке данных с {url:s} : {error:s}",
|
"download_unknown_error": "Ошибка при загрузке данных с {url:s} : {error:s}",
|
||||||
"instance_already_running": "Процесс уже запущен",
|
"instance_already_running": "Операция YunoHost уже запущена. Пожалуйста, подождите, пока он закончится, прежде чем запускать другой.",
|
||||||
"ldap_operation_error": "Ошибка в процессе работы LDAP",
|
"ldap_operation_error": "Ошибка в процессе работы LDAP",
|
||||||
"root_required": "Чтобы выполнить это действие, вы должны иметь права root",
|
"root_required": "Чтобы выполнить это действие, вы должны иметь права root",
|
||||||
"corrupted_json": "Повреждённый json получен от {ressource:s} (причина: {error:s})",
|
"corrupted_json": "Повреждённый json получен от {ressource:s} (причина: {error:s})",
|
||||||
"command_unknown": "Команда '{command:s}' неизвестна ?"
|
"command_unknown": "Команда '{command:s}' неизвестна ?",
|
||||||
|
"warn_the_user_that_lock_is_acquired": "другая команда только что завершилась, теперь запускает эту команду",
|
||||||
|
"warn_the_user_about_waiting_lock_again": "Все еще жду...",
|
||||||
|
"warn_the_user_about_waiting_lock": "Сейчас запускается еще одна команда YunoHost, мы ждем ее завершения, прежде чем запустить эту",
|
||||||
|
"download_bad_status_code": "{url:s} вернул код состояния {code:s}",
|
||||||
|
"error_changing_file_permissions": "Ошибка при изменении разрешений для {path:s}: {error:s}",
|
||||||
|
"corrupted_toml": "Поврежденный том, прочитанный из {ressource:s} (причина: {error:s})",
|
||||||
|
"unable_retrieve_session": "Невозможно получить сеанс, так как '{exception}'",
|
||||||
|
"ldap_server_down": "Невозможно связаться с сервером LDAP",
|
||||||
|
"invalid_usage": "Неправильное использование, передайте --help, чтобы увидеть помощь",
|
||||||
|
"invalid_token": "Неверный токен - пожалуйста, авторизуйтесь",
|
||||||
|
"info": "Информация:"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,58 @@
|
||||||
{}
|
{
|
||||||
|
"warn_the_user_about_waiting_lock_again": "Väntar fortfarande …",
|
||||||
|
"download_bad_status_code": "{url:s} svarade med statuskod {code:s}",
|
||||||
|
"download_timeout": "Gav upp eftersom {url:s} tog för lång tid på sig att svara.",
|
||||||
|
"download_ssl_error": "Ett SSL-fel påträffades vid anslutning till {url:s}",
|
||||||
|
"cannot_write_file": "Kunde inte skriva till filen {file:s} (orsak: {error:s})",
|
||||||
|
"cannot_open_file": "Kunde inte öppna filen {file:s} (orsak: {error:s})",
|
||||||
|
"websocket_request_expected": "Förväntade en WebSocket-förfrågan",
|
||||||
|
"warning": "Varning:",
|
||||||
|
"values_mismatch": "Värdena stämmer inte överens",
|
||||||
|
"unknown_user": "Okänd användare '{user}'",
|
||||||
|
"unknown_group": "Okänd grupp '{group}'",
|
||||||
|
"success": "Lyckades!",
|
||||||
|
"server_already_running": "En server använder redan den porten",
|
||||||
|
"root_required": "Du måste vara inloggad som root för att utföra den här åtgärden",
|
||||||
|
"pattern_not_match": "Stämmer inte in på mönstret",
|
||||||
|
"operation_interrupted": "Behandling avbruten",
|
||||||
|
"not_logged_in": "Du är inte inloggad",
|
||||||
|
"logged_in": "Inloggad",
|
||||||
|
"ldap_attribute_already_exists": "Attributet '{attribute}' finns redan med värdet '{value}'",
|
||||||
|
"invalid_password": "Ogiltigt lösenord",
|
||||||
|
"invalid_argument": "Ogiltig parameter '{argument}': {error}",
|
||||||
|
"logged_out": "Utloggad",
|
||||||
|
"info": "Info:",
|
||||||
|
"folder_not_exist": "Katalogen finns inte",
|
||||||
|
"folder_exists": "Katalogen finns redan: '{path}'",
|
||||||
|
"file_not_exist": "Filen finns inte: '{path}'",
|
||||||
|
"file_exists": "Filen finns redan: '{path}'",
|
||||||
|
"error_see_log": "Ett fel har inträffat. Kolla gärna i loggfilerna för mer information, de finns i /var/log/yunohost/.",
|
||||||
|
"error": "Fel:",
|
||||||
|
"deprecated_command_alias": "'{prog} {old}' rekommenderas inte längre och kommer tas bort i framtiden, använd '{prog} {new}' istället",
|
||||||
|
"deprecated_command": "'{prog} {command}' rekommenderas inte längre och kommer tas bort i framtiden",
|
||||||
|
"confirm": "Bekräfta {prompt}",
|
||||||
|
"colon": "{}: ",
|
||||||
|
"argument_required": "Parametern '{argument}' krävs",
|
||||||
|
"password": "Lösenord",
|
||||||
|
"warn_the_user_that_lock_is_acquired": "det andra kommandot har bara slutförts, nu startar du det här kommandot",
|
||||||
|
"warn_the_user_about_waiting_lock": "Ett annat YunoHost-kommando körs just nu, vi väntar på att det ska slutföras innan det här körs",
|
||||||
|
"command_unknown": "Kommando '{command:s}' okänd?",
|
||||||
|
"download_unknown_error": "Fel vid nedladdning av data från {url:s}: {error:s}",
|
||||||
|
"invalid_url": "Ogiltig url {url:s} (finns den här webbplatsen?)",
|
||||||
|
"error_changing_file_permissions": "Fel vid ändring av behörigheter för {path:s}: {error:s}",
|
||||||
|
"error_removing": "Fel vid borttagning av {path:s}: {error:s}",
|
||||||
|
"error_writing_file": "Fel vid skrivning av fil {file:s}: {error:s}",
|
||||||
|
"corrupted_toml": "Korrupt toml läst från {ressource:s} (anledning: {error:s})",
|
||||||
|
"corrupted_yaml": "Skadad yaml läst från {ressource:s} (anledning: {error:s})",
|
||||||
|
"corrupted_json": "Skadad json läst från {ressource:s} (anledning: {error:s})",
|
||||||
|
"unknown_error_reading_file": "Okänt fel vid försök att läsa filen {file:s} (anledning: {error:s})",
|
||||||
|
"unable_retrieve_session": "Det gick inte att hämta sessionen eftersom '{exception}'",
|
||||||
|
"unable_authenticate": "Det går inte att verifiera",
|
||||||
|
"ldap_server_down": "Det går inte att nå LDAP-servern",
|
||||||
|
"ldap_operation_error": "Ett fel inträffade under LDAP-drift",
|
||||||
|
"invalid_usage": "Ogiltig användning, pass --help för att se hjälp",
|
||||||
|
"invalid_token": "Ogiltigt token - verifiera",
|
||||||
|
"instance_already_running": "Det finns redan en YunoHost-operation. Vänta tills den är klar innan du kör en annan.",
|
||||||
|
"authentication_required_long": "Autentisering krävs för att utföra denna åtgärd",
|
||||||
|
"authentication_required": "Autentisering krävs"
|
||||||
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
"colon": "{}: ",
|
"colon": "{}: ",
|
||||||
"confirm": "{prompt}'i doğrulayın",
|
"confirm": "{prompt}'i doğrulayın",
|
||||||
"error": "Hata:",
|
"error": "Hata:",
|
||||||
"error_see_log": "Bir hata oluştu. Detaylar için lütfen loga bakınız",
|
"error_see_log": "Bir hata oluştu. Ayrıntılar için lütfen günlüklere bakın, bunlar /var/log/yunohost/ dizinindedir.",
|
||||||
"instance_already_running": "Uygulama zaten çalışıyor",
|
"instance_already_running": "Halihazırda bir YunoHost operasyonu var. Lütfen başka bir tane çalıştırmadan önce bitmesini bekleyin.",
|
||||||
"invalid_argument": "Geçersiz argüman '{argument}': {error}",
|
"invalid_argument": "Geçersiz argüman '{argument}': {error}",
|
||||||
"invalid_password": "Geçersiz parola",
|
"invalid_password": "Geçersiz parola",
|
||||||
"ldap_attribute_already_exists": "'{attribute}={value}' özelliği zaten mevcut",
|
"ldap_attribute_already_exists": "'{attribute}={value}' özelliği zaten mevcut",
|
||||||
|
@ -24,8 +24,37 @@
|
||||||
"server_already_running": "Bu portta zaten çalışan bir sunucu var",
|
"server_already_running": "Bu portta zaten çalışan bir sunucu var",
|
||||||
"success": "İşlem Başarılı!",
|
"success": "İşlem Başarılı!",
|
||||||
"unable_authenticate": "Yetkilendirme başarısız",
|
"unable_authenticate": "Yetkilendirme başarısız",
|
||||||
"unable_retrieve_session": "Oturum bilgileri alınamadı",
|
"unable_retrieve_session": "'{exception}' nedeniyle oturum alınamadı",
|
||||||
"values_mismatch": "Değerler uyuşmuyor",
|
"values_mismatch": "Değerler uyuşmuyor",
|
||||||
"warning": "Uyarı:",
|
"warning": "Uyarı:",
|
||||||
"websocket_request_expected": "WebSocket isteği gerekli"
|
"websocket_request_expected": "WebSocket isteği gerekli",
|
||||||
|
"warn_the_user_that_lock_is_acquired": "diğer komut şimdi tamamlandı, şimdi bu komutu başlatıyor",
|
||||||
|
"warn_the_user_about_waiting_lock_again": "Hala bekliyor...",
|
||||||
|
"warn_the_user_about_waiting_lock": "Başka bir YunoHost komutu şu anda çalışıyor, bunu çalıştırmadan önce bitmesini bekliyoruz",
|
||||||
|
"command_unknown": "'{Command:s}' komutu bilinmiyor mu?",
|
||||||
|
"download_bad_status_code": "{url:s} döndürülen durum kodu {code:s}",
|
||||||
|
"download_unknown_error": "{url:s} adresinden veri indirilirken hata oluştu: {error:s}",
|
||||||
|
"download_timeout": "{url:s} yanıtlaması çok uzun sürdü, pes etti.",
|
||||||
|
"download_ssl_error": "{url:s} ağına bağlanırken SSL hatası",
|
||||||
|
"invalid_url": "Geçersiz url {url:s} (bu site var mı?)",
|
||||||
|
"error_changing_file_permissions": "{Path:s} için izinler değiştirilirken hata oluştu: {error:s}",
|
||||||
|
"error_removing": "{Path:s} kaldırılırken hata oluştu: {error:s}",
|
||||||
|
"error_writing_file": "{File:s} dosyası yazılırken hata oluştu: {error:s}",
|
||||||
|
"corrupted_toml": "{Ressource:s} kaynağından okunan bozuk toml (nedeni: {hata:s})",
|
||||||
|
"corrupted_yaml": "{Ressource:s} kaynağından bozuk yaml okunuyor (nedeni: {error:s})",
|
||||||
|
"corrupted_json": "{Ressource:s} adresinden okunan bozuk json (nedeni: {error:s})",
|
||||||
|
"unknown_error_reading_file": "{File:s} dosyasını okumaya çalışırken bilinmeyen hata (nedeni: {error:s})",
|
||||||
|
"cannot_write_file": "{File:s} dosyası yazılamadı (nedeni: {error:s})",
|
||||||
|
"cannot_open_file": "{File:s} dosyası açılamadı (nedeni: {error:s})",
|
||||||
|
"unknown_user": "Bilinmeyen '{user}' kullanıcı",
|
||||||
|
"unknown_group": "Bilinmeyen '{group}' grubu",
|
||||||
|
"invalid_usage": "Geçersiz kullanım, yardım görmek için --help iletin",
|
||||||
|
"invalid_token": "Geçersiz simge - lütfen kimlik doğrulaması yapın",
|
||||||
|
"info": "Bilgi:",
|
||||||
|
"folder_not_exist": "Klasör mevcut değil",
|
||||||
|
"folder_exists": "Klasör zaten var: '{path}'",
|
||||||
|
"file_not_exist": "Dosya mevcut değil: '{path}'",
|
||||||
|
"file_exists": "Dosya zaten var: '{path}'",
|
||||||
|
"deprecated_command_alias": "'{prog} {old}' kullanımdan kaldırıldı ve gelecekte kaldırılacak, bunun yerine '{prog} {new}' kullanın",
|
||||||
|
"deprecated_command": "'{prog} {command}' kullanımdan kaldırıldı ve gelecekte kaldırılacak"
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ __all__ = [
|
||||||
"api",
|
"api",
|
||||||
"cli",
|
"cli",
|
||||||
"m18n",
|
"m18n",
|
||||||
|
"msignals",
|
||||||
"env",
|
"env",
|
||||||
"init_interface",
|
"init_interface",
|
||||||
"MoulinetteError",
|
"MoulinetteError",
|
||||||
|
|
|
@ -89,6 +89,8 @@ class CommentParameter(_ExtraParameter):
|
||||||
skipped_iface = ["api"]
|
skipped_iface = ["api"]
|
||||||
|
|
||||||
def __call__(self, message, arg_name, arg_value):
|
def __call__(self, message, arg_name, arg_value):
|
||||||
|
if arg_value:
|
||||||
|
return
|
||||||
return msignals.display(m18n.n(message))
|
return msignals.display(m18n.n(message))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -191,7 +193,7 @@ class PatternParameter(_ExtraParameter):
|
||||||
v = arg_value
|
v = arg_value
|
||||||
|
|
||||||
if v and not re.match(pattern, v or "", re.UNICODE):
|
if v and not re.match(pattern, v or "", re.UNICODE):
|
||||||
logger.debug(
|
logger.warning(
|
||||||
"argument value '%s' for '%s' doesn't match pattern '%s'",
|
"argument value '%s' for '%s' doesn't match pattern '%s'",
|
||||||
v,
|
v,
|
||||||
arg_name,
|
arg_name,
|
||||||
|
@ -233,14 +235,14 @@ class RequiredParameter(_ExtraParameter):
|
||||||
|
|
||||||
def __call__(self, required, arg_name, arg_value):
|
def __call__(self, required, arg_name, arg_value):
|
||||||
if required and (arg_value is None or arg_value == ""):
|
if required and (arg_value is None or arg_value == ""):
|
||||||
logger.debug("argument '%s' is required", arg_name)
|
logger.warning("argument '%s' is required", arg_name)
|
||||||
raise MoulinetteError("argument_required", argument=arg_name)
|
raise MoulinetteError("argument_required", argument=arg_name)
|
||||||
return arg_value
|
return arg_value
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate(value, arg_name):
|
def validate(value, arg_name):
|
||||||
if not isinstance(value, bool):
|
if not isinstance(value, bool):
|
||||||
raise TypeError("parameter value must be a list, got %r" % value)
|
raise TypeError("parameter value must be a boolean, got %r" % value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,11 @@ class BaseAuthenticator(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name, vendor, parameters, extra):
|
||||||
self._name = name
|
self._name = name
|
||||||
|
self.vendor = vendor
|
||||||
self.is_authenticated = False
|
self.is_authenticated = False
|
||||||
|
self.extra = extra
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
|
|
|
@ -18,11 +18,12 @@ class Authenticator(BaseAuthenticator):
|
||||||
|
|
||||||
def __init__(self, name, vendor, parameters, extra):
|
def __init__(self, name, vendor, parameters, extra):
|
||||||
logger.debug("initialize authenticator dummy")
|
logger.debug("initialize authenticator dummy")
|
||||||
super(Authenticator, self).__init__(name)
|
|
||||||
|
|
||||||
def authenticate(self, password):
|
super(Authenticator, self).__init__(name, vendor, parameters, extra)
|
||||||
|
|
||||||
if not password == "Yoloswag":
|
def authenticate(self, password=None):
|
||||||
raise MoulinetteError("Invalid password!")
|
|
||||||
|
if not password == self.name:
|
||||||
|
raise MoulinetteError("invalid_password")
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
|
@ -39,18 +39,20 @@ class Authenticator(BaseAuthenticator):
|
||||||
self.basedn = parameters["base_dn"]
|
self.basedn = parameters["base_dn"]
|
||||||
self.userdn = parameters["user_rdn"]
|
self.userdn = parameters["user_rdn"]
|
||||||
self.extra = extra
|
self.extra = extra
|
||||||
|
self.sasldn = "cn=external,cn=auth"
|
||||||
|
self.adminuser = "admin"
|
||||||
|
self.admindn = "cn=%s,dc=yunohost,dc=org" % self.adminuser
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"initialize authenticator '%s' with: uri='%s', "
|
"initialize authenticator '%s' with: uri='%s', "
|
||||||
"base_dn='%s', user_rdn='%s'",
|
"base_dn='%s', user_rdn='%s'",
|
||||||
name,
|
name,
|
||||||
self.uri,
|
self._get_uri(),
|
||||||
self.basedn,
|
self.basedn,
|
||||||
self.userdn,
|
self.userdn,
|
||||||
)
|
)
|
||||||
super(Authenticator, self).__init__(name)
|
super(Authenticator, self).__init__(name, vendor, parameters, extra)
|
||||||
|
|
||||||
if self.userdn:
|
if self.userdn and self.sasldn in self.userdn:
|
||||||
if "cn=external,cn=auth" in self.userdn:
|
|
||||||
self.authenticate(None)
|
self.authenticate(None)
|
||||||
else:
|
else:
|
||||||
self.con = None
|
self.con = None
|
||||||
|
@ -66,13 +68,13 @@ class Authenticator(BaseAuthenticator):
|
||||||
|
|
||||||
# Implement virtual methods
|
# Implement virtual methods
|
||||||
|
|
||||||
def authenticate(self, password):
|
def authenticate(self, password=None):
|
||||||
try:
|
try:
|
||||||
con = ldap.ldapobject.ReconnectLDAPObject(
|
con = ldap.ldapobject.ReconnectLDAPObject(
|
||||||
self.uri, retry_max=10, retry_delay=0.5
|
self._get_uri(), retry_max=10, retry_delay=0.5
|
||||||
)
|
)
|
||||||
if self.userdn:
|
if self.userdn:
|
||||||
if "cn=external,cn=auth" in self.userdn:
|
if self.sasldn in self.userdn:
|
||||||
con.sasl_non_interactive_bind_s("EXTERNAL")
|
con.sasl_non_interactive_bind_s("EXTERNAL")
|
||||||
else:
|
else:
|
||||||
con.simple_bind_s(self.userdn, password)
|
con.simple_bind_s(self.userdn, password)
|
||||||
|
@ -86,12 +88,14 @@ class Authenticator(BaseAuthenticator):
|
||||||
|
|
||||||
# Check that we are indeed logged in with the right identity
|
# Check that we are indeed logged in with the right identity
|
||||||
try:
|
try:
|
||||||
who = con.whoami_s()
|
# whoami_s return dn:..., then delete these 3 characters
|
||||||
|
who = con.whoami_s()[3:]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Error during ldap authentication process: %s", e)
|
logger.warning("Error during ldap authentication process: %s", e)
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
if who[3:] != self.userdn:
|
# FIXME: During SASL bind whoami from the test server return the admindn while userdn is returned normally :
|
||||||
|
if not (who == self.admindn or who == self.userdn):
|
||||||
raise MoulinetteError("Not logged in with the expected userdn ?!")
|
raise MoulinetteError("Not logged in with the expected userdn ?!")
|
||||||
else:
|
else:
|
||||||
self.con = con
|
self.con = con
|
||||||
|
@ -107,9 +111,7 @@ class Authenticator(BaseAuthenticator):
|
||||||
salt = "$6$" + salt + "$"
|
salt = "$6$" + salt + "$"
|
||||||
return "{CRYPT}" + crypt.crypt(str(password), salt)
|
return "{CRYPT}" + crypt.crypt(str(password), salt)
|
||||||
|
|
||||||
hashed_password = self.search(
|
hashed_password = self.search(self.admindn, attrs=["userPassword"])[0]
|
||||||
"cn=admin,dc=yunohost,dc=org", attrs=["userPassword"]
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
# post-install situation, password is not already set
|
# post-install situation, password is not already set
|
||||||
if "userPassword" not in hashed_password or not hashed_password["userPassword"]:
|
if "userPassword" not in hashed_password or not hashed_password["userPassword"]:
|
||||||
|
@ -117,7 +119,10 @@ class Authenticator(BaseAuthenticator):
|
||||||
|
|
||||||
# we aren't using sha-512 but something else that is weaker, proceed to upgrade
|
# we aren't using sha-512 but something else that is weaker, proceed to upgrade
|
||||||
if not hashed_password["userPassword"][0].startswith("{CRYPT}$6$"):
|
if not hashed_password["userPassword"][0].startswith("{CRYPT}$6$"):
|
||||||
self.update("cn=admin", {"userPassword": _hash_user_password(password),})
|
self.update(
|
||||||
|
"cn=%s" % self.adminuser,
|
||||||
|
{"userPassword": [_hash_user_password(password)]},
|
||||||
|
)
|
||||||
|
|
||||||
# Additional LDAP methods
|
# Additional LDAP methods
|
||||||
# TODO: Review these methods
|
# TODO: Review these methods
|
||||||
|
@ -151,7 +156,7 @@ class Authenticator(BaseAuthenticator):
|
||||||
attrs,
|
attrs,
|
||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
raise MoulinetteError("ldap_operation_error")
|
raise MoulinetteError("ldap_operation_error", action="search")
|
||||||
|
|
||||||
result_list = []
|
result_list = []
|
||||||
if not attrs or "dn" not in attrs:
|
if not attrs or "dn" not in attrs:
|
||||||
|
@ -187,7 +192,7 @@ class Authenticator(BaseAuthenticator):
|
||||||
attr_dict,
|
attr_dict,
|
||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
raise MoulinetteError("ldap_operation_error")
|
raise MoulinetteError("ldap_operation_error", action="add")
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -211,7 +216,7 @@ class Authenticator(BaseAuthenticator):
|
||||||
rdn,
|
rdn,
|
||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
raise MoulinetteError("ldap_operation_error")
|
raise MoulinetteError("ldap_operation_error", action="remove")
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -232,10 +237,15 @@ class Authenticator(BaseAuthenticator):
|
||||||
actual_entry = self.search(base=dn, attrs=None)
|
actual_entry = self.search(base=dn, attrs=None)
|
||||||
ldif = modlist.modifyModlist(actual_entry[0], attr_dict, ignore_oldexistent=1)
|
ldif = modlist.modifyModlist(actual_entry[0], attr_dict, ignore_oldexistent=1)
|
||||||
|
|
||||||
|
if ldif == []:
|
||||||
|
logger.warning("Nothing to update in LDAP")
|
||||||
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if new_rdn:
|
if new_rdn:
|
||||||
self.con.rename_s(dn, new_rdn)
|
self.con.rename_s(dn, new_rdn)
|
||||||
dn = new_rdn + "," + self.basedn
|
new_base = dn.split(",", 1)[1]
|
||||||
|
dn = new_rdn + "," + new_base
|
||||||
|
|
||||||
self.con.modify_ext_s(dn, ldif)
|
self.con.modify_ext_s(dn, ldif)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -247,7 +257,7 @@ class Authenticator(BaseAuthenticator):
|
||||||
new_rdn,
|
new_rdn,
|
||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
raise MoulinetteError("ldap_operation_error")
|
raise MoulinetteError("ldap_operation_error", action="update")
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -284,7 +294,7 @@ class Authenticator(BaseAuthenticator):
|
||||||
value_dict -- Dictionnary of attributes/values to check
|
value_dict -- Dictionnary of attributes/values to check
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None | list with Fist conflict attribute name and value
|
None | tuple with Fist conflict attribute name and value
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for attr, value in value_dict.items():
|
for attr, value in value_dict.items():
|
||||||
|
@ -293,3 +303,6 @@ class Authenticator(BaseAuthenticator):
|
||||||
else:
|
else:
|
||||||
return (attr, value)
|
return (attr, value)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _get_uri(self):
|
||||||
|
return self.uri
|
||||||
|
|
|
@ -451,12 +451,14 @@ class MoulinetteLock(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
base_lockfile = "/var/run/moulinette_%s.lock"
|
||||||
|
|
||||||
def __init__(self, namespace, timeout=None, interval=0.5):
|
def __init__(self, namespace, timeout=None, interval=0.5):
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
|
|
||||||
self._lockfile = "/var/run/moulinette_%s.lock" % namespace
|
self._lockfile = self.base_lockfile % namespace
|
||||||
self._stale_checked = False
|
self._stale_checked = False
|
||||||
self._locked = False
|
self._locked = False
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ class BaseActionsMapParser(object):
|
||||||
# Each parser classes must implement these methods.
|
# Each parser classes must implement these methods.
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_arg_names(self, name, full):
|
def format_arg_names(name, full):
|
||||||
"""Format argument name
|
"""Format argument name
|
||||||
|
|
||||||
Format agument name depending on its 'full' parameter and return
|
Format agument name depending on its 'full' parameter and return
|
||||||
|
@ -70,9 +70,7 @@ class BaseActionsMapParser(object):
|
||||||
A list of option strings
|
A list of option strings
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError(
|
raise NotImplementedError("derived class must override this method")
|
||||||
"derived class '%s' must override this method" % self.__class__.__name__
|
|
||||||
)
|
|
||||||
|
|
||||||
def has_global_parser(self):
|
def has_global_parser(self):
|
||||||
return False
|
return False
|
||||||
|
@ -156,7 +154,8 @@ class BaseActionsMapParser(object):
|
||||||
|
|
||||||
# Arguments helpers
|
# Arguments helpers
|
||||||
|
|
||||||
def prepare_action_namespace(self, tid, namespace=None):
|
@staticmethod
|
||||||
|
def prepare_action_namespace(tid, namespace=None):
|
||||||
"""Prepare the namespace for a given action"""
|
"""Prepare the namespace for a given action"""
|
||||||
# Validate tid and namespace
|
# Validate tid and namespace
|
||||||
if not isinstance(tid, tuple) and (
|
if not isinstance(tid, tuple) and (
|
||||||
|
@ -245,6 +244,9 @@ class BaseActionsMapParser(object):
|
||||||
elif ifaces is False:
|
elif ifaces is False:
|
||||||
conf["authenticate"] = False
|
conf["authenticate"] = False
|
||||||
elif isinstance(ifaces, list):
|
elif isinstance(ifaces, list):
|
||||||
|
if "all" in ifaces:
|
||||||
|
conf["authenticate"] = "all"
|
||||||
|
else:
|
||||||
# Store only if authentication is needed
|
# Store only if authentication is needed
|
||||||
conf["authenticate"] = True if self.interface in ifaces else False
|
conf["authenticate"] = True if self.interface in ifaces else False
|
||||||
else:
|
else:
|
||||||
|
@ -256,27 +258,22 @@ class BaseActionsMapParser(object):
|
||||||
raise MoulinetteError("error_see_log")
|
raise MoulinetteError("error_see_log")
|
||||||
|
|
||||||
# -- 'authenticator'
|
# -- 'authenticator'
|
||||||
try:
|
auth = configuration.get("authenticator", "default")
|
||||||
auth = configuration["authenticator"]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if not is_global and isinstance(auth, str):
|
if not is_global and isinstance(auth, str):
|
||||||
try:
|
|
||||||
# Store needed authenticator profile
|
# Store needed authenticator profile
|
||||||
conf["authenticator"] = self.global_conf["authenticator"][auth]
|
if auth not in self.global_conf["authenticator"]:
|
||||||
except KeyError:
|
|
||||||
logger.error(
|
logger.error(
|
||||||
"requesting profile '%s' which is undefined in "
|
"requesting profile '%s' which is undefined in "
|
||||||
"global configuration of 'authenticator'",
|
"global configuration of 'authenticator'",
|
||||||
auth,
|
auth,
|
||||||
)
|
)
|
||||||
raise MoulinetteError("error_see_log")
|
raise MoulinetteError("error_see_log")
|
||||||
|
else:
|
||||||
|
conf["authenticator"] = auth
|
||||||
elif is_global and isinstance(auth, dict):
|
elif is_global and isinstance(auth, dict):
|
||||||
if len(auth) == 0:
|
if len(auth) == 0:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"no profile defined in global configuration "
|
"no profile defined in global configuration " "for 'authenticator'"
|
||||||
"for 'authenticator'"
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
auths = {}
|
auths = {}
|
||||||
|
|
|
@ -256,10 +256,8 @@ class _ActionsMapPlugin(object):
|
||||||
kwargs["password"] = request.POST["password"]
|
kwargs["password"] = request.POST["password"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise HTTPBadRequestResponse("Missing password parameter")
|
raise HTTPBadRequestResponse("Missing password parameter")
|
||||||
try:
|
|
||||||
kwargs["profile"] = request.POST["profile"]
|
kwargs["profile"] = request.POST.get("profile", "default")
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
return callback(**kwargs)
|
return callback(**kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
@ -351,7 +349,7 @@ class _ActionsMapPlugin(object):
|
||||||
|
|
||||||
# Routes callbacks
|
# Routes callbacks
|
||||||
|
|
||||||
def login(self, password, profile="default"):
|
def login(self, password, profile):
|
||||||
"""Log in to an authenticator profile
|
"""Log in to an authenticator profile
|
||||||
|
|
||||||
Attempt to authenticate to a given authenticator profile and
|
Attempt to authenticate to a given authenticator profile and
|
||||||
|
@ -406,13 +404,16 @@ class _ActionsMapPlugin(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
s_id = request.get_cookie("session.id")
|
s_id = request.get_cookie("session.id")
|
||||||
try:
|
|
||||||
# We check that there's a (signed) session.hash available
|
# We check that there's a (signed) session.hash available
|
||||||
# for additional security ?
|
# for additional security ?
|
||||||
# (An attacker could not craft such signed hashed ? (FIXME : need to make sure of this))
|
# (An attacker could not craft such signed hashed ? (FIXME : need to make sure of this))
|
||||||
|
try:
|
||||||
s_secret = self.secrets[s_id]
|
s_secret = self.secrets[s_id]
|
||||||
request.get_cookie("session.tokens", secret=s_secret, default={})[profile]
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
s_secret = {}
|
||||||
|
if profile not in request.get_cookie(
|
||||||
|
"session.tokens", secret=s_secret, default={}
|
||||||
|
):
|
||||||
raise HTTPUnauthorizedResponse(m18n.g("not_logged_in"))
|
raise HTTPUnauthorizedResponse(m18n.g("not_logged_in"))
|
||||||
else:
|
else:
|
||||||
del self.secrets[s_id]
|
del self.secrets[s_id]
|
||||||
|
@ -661,24 +662,28 @@ class ActionsMapParser(BaseActionsMapParser):
|
||||||
# Return the created parser
|
# Return the created parser
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def auth_required(self, args, route, **kwargs):
|
def auth_required(self, args, **kwargs):
|
||||||
try:
|
try:
|
||||||
# Retrieve the tid for the route
|
# Retrieve the tid for the route
|
||||||
tid, _ = self._parsers[route]
|
tid, _ = self._parsers[kwargs.get("route")]
|
||||||
if not self.get_conf(tid, "authenticate"):
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# TODO: In the future, we could make the authentication
|
|
||||||
# dependent of the route being hit ...
|
|
||||||
# e.g. in the context of friend2friend stuff that could
|
|
||||||
# auth with some custom auth system to access some
|
|
||||||
# data with something like :
|
|
||||||
# return self.get_conf(tid, 'authenticator')
|
|
||||||
return "default"
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.error("no argument parser found for route '%s'", route)
|
logger.error("no argument parser found for route '%s'", kwargs.get("route"))
|
||||||
raise MoulinetteError("error_see_log")
|
raise MoulinetteError("error_see_log")
|
||||||
|
|
||||||
|
if self.get_conf(tid, "authenticate"):
|
||||||
|
authenticator = self.get_conf(tid, "authenticator")
|
||||||
|
|
||||||
|
# If several authenticator, use the default one
|
||||||
|
if isinstance(authenticator, dict):
|
||||||
|
if "default" in authenticator:
|
||||||
|
authenticator = "default"
|
||||||
|
else:
|
||||||
|
# TODO which one should we use?
|
||||||
|
pass
|
||||||
|
return authenticator
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def parse_args(self, args, route, **kwargs):
|
def parse_args(self, args, route, **kwargs):
|
||||||
"""Parse arguments
|
"""Parse arguments
|
||||||
|
|
||||||
|
|
|
@ -287,7 +287,7 @@ class ActionsMapParser(BaseActionsMapParser):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_arg_names(name, full):
|
def format_arg_names(name, full):
|
||||||
if name[0] == "-" and full:
|
if name.startswith("-") and full:
|
||||||
return [name, full]
|
return [name, full]
|
||||||
return [name]
|
return [name]
|
||||||
|
|
||||||
|
@ -379,7 +379,16 @@ class ActionsMapParser(BaseActionsMapParser):
|
||||||
|
|
||||||
tid = getattr(ret, "_tid", None)
|
tid = getattr(ret, "_tid", None)
|
||||||
if self.get_conf(tid, "authenticate"):
|
if self.get_conf(tid, "authenticate"):
|
||||||
return self.get_conf(tid, "authenticator")
|
authenticator = self.get_conf(tid, "authenticator")
|
||||||
|
|
||||||
|
# If several authenticator, use the default one
|
||||||
|
if isinstance(authenticator, dict):
|
||||||
|
if "default" in authenticator:
|
||||||
|
authenticator = "default"
|
||||||
|
else:
|
||||||
|
# TODO which one should we use?
|
||||||
|
pass
|
||||||
|
return authenticator
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -446,6 +455,9 @@ class Interface(BaseInterface):
|
||||||
# Set handler for authentication
|
# Set handler for authentication
|
||||||
if password:
|
if password:
|
||||||
msignals.set_handler("authenticate", lambda a: a(password=password))
|
msignals.set_handler("authenticate", lambda a: a(password=password))
|
||||||
|
else:
|
||||||
|
if os.isatty(1):
|
||||||
|
msignals.set_handler("authenticate", self._do_authenticate)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ret = self.actionsmap.process(args, timeout=timeout)
|
ret = self.actionsmap.process(args, timeout=timeout)
|
||||||
|
|
|
@ -100,9 +100,7 @@ def read_toml(file_path):
|
||||||
try:
|
try:
|
||||||
loaded_toml = toml.loads(file_content, _dict=OrderedDict)
|
loaded_toml = toml.loads(file_content, _dict=OrderedDict)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise MoulinetteError(
|
raise MoulinetteError("corrupted_toml", ressource=file_path, error=str(e))
|
||||||
errno.EINVAL, m18n.g("corrupted_toml", ressource=file_path, error=str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
return loaded_toml
|
return loaded_toml
|
||||||
|
|
||||||
|
@ -255,7 +253,7 @@ def write_to_yaml(file_path, data):
|
||||||
raise MoulinetteError("error_writing_file", file=file_path, error=str(e))
|
raise MoulinetteError("error_writing_file", file=file_path, error=str(e))
|
||||||
|
|
||||||
|
|
||||||
def mkdir(path, mode=0o777, parents=False, uid=None, gid=None, force=False):
|
def mkdir(path, mode=0o0777, parents=False, uid=None, gid=None, force=False):
|
||||||
"""Create a directory with optional features
|
"""Create a directory with optional features
|
||||||
|
|
||||||
Create a directory and optionaly set its permissions to mode and its
|
Create a directory and optionaly set its permissions to mode and its
|
||||||
|
@ -290,7 +288,9 @@ def mkdir(path, mode=0o777, parents=False, uid=None, gid=None, force=False):
|
||||||
|
|
||||||
# Create directory and set permissions
|
# Create directory and set permissions
|
||||||
try:
|
try:
|
||||||
|
oldmask = os.umask(000)
|
||||||
os.mkdir(path, mode)
|
os.mkdir(path, mode)
|
||||||
|
os.umask(oldmask)
|
||||||
except OSError:
|
except OSError:
|
||||||
# mimic Python3.2+ os.makedirs exist_ok behaviour
|
# mimic Python3.2+ os.makedirs exist_ok behaviour
|
||||||
if not force or not os.path.isdir(path):
|
if not force or not os.path.isdir(path):
|
||||||
|
|
|
@ -16,6 +16,16 @@ from logging import (
|
||||||
CRITICAL,
|
CRITICAL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"NOTSET", # noqa
|
||||||
|
"DEBUG",
|
||||||
|
"INFO",
|
||||||
|
"WARNING",
|
||||||
|
"ERROR",
|
||||||
|
"CRITICAL",
|
||||||
|
"SUCCESS",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# Global configuration and functions -----------------------------------
|
# Global configuration and functions -----------------------------------
|
||||||
|
|
||||||
|
@ -35,7 +45,7 @@ DEFAULT_LOGGING = {
|
||||||
"stream": "ext://sys.stdout",
|
"stream": "ext://sys.stdout",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"loggers": {"moulinette": {"level": "DEBUG", "handlers": ["console"],},},
|
"loggers": {"moulinette": {"level": "DEBUG", "handlers": ["console"]}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,12 +22,12 @@ def download_text(url, timeout=30, expected_status_code=200):
|
||||||
# Download file
|
# Download file
|
||||||
try:
|
try:
|
||||||
r = requests.get(url, timeout=timeout)
|
r = requests.get(url, timeout=timeout)
|
||||||
# Invalid URL
|
|
||||||
except requests.exceptions.ConnectionError:
|
|
||||||
raise MoulinetteError("invalid_url", url=url)
|
|
||||||
# SSL exceptions
|
# SSL exceptions
|
||||||
except requests.exceptions.SSLError:
|
except requests.exceptions.SSLError:
|
||||||
raise MoulinetteError("download_ssl_error", url=url)
|
raise MoulinetteError("download_ssl_error", url=url)
|
||||||
|
# Invalid URL
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
raise MoulinetteError("invalid_url", url=url)
|
||||||
# Timeout exceptions
|
# Timeout exceptions
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
raise MoulinetteError("download_timeout", url=url)
|
raise MoulinetteError("download_timeout", url=url)
|
||||||
|
|
|
@ -43,9 +43,16 @@ class AsynchronousFileReader(Process):
|
||||||
else:
|
else:
|
||||||
data = ""
|
data = ""
|
||||||
while True:
|
while True:
|
||||||
|
try:
|
||||||
# Try to read (non-blockingly) a few bytes, append them to
|
# Try to read (non-blockingly) a few bytes, append them to
|
||||||
# the buffer
|
# the buffer
|
||||||
data += os.read(self._fd, 50)
|
data += os.read(self._fd, 50)
|
||||||
|
except Exception as e:
|
||||||
|
print(
|
||||||
|
"from moulinette.utils.stream: could not read file descriptor : %s"
|
||||||
|
% str(e)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
# If nobody's writing in there anymore, get out
|
# If nobody's writing in there anymore, get out
|
||||||
if not data and os.fstat(self._fd).st_nlink == 0:
|
if not data and os.fstat(self._fd).st_nlink == 0:
|
||||||
|
|
4
setup.py
4
setup.py
|
@ -34,6 +34,9 @@ setup(name='Moulinette',
|
||||||
'pytz',
|
'pytz',
|
||||||
'pyyaml',
|
'pyyaml',
|
||||||
'toml',
|
'toml',
|
||||||
|
'python-ldap',
|
||||||
|
'gevent-websocket',
|
||||||
|
'bottle',
|
||||||
],
|
],
|
||||||
tests_require=[
|
tests_require=[
|
||||||
'pytest',
|
'pytest',
|
||||||
|
@ -42,5 +45,6 @@ setup(name='Moulinette',
|
||||||
'pytest-mock',
|
'pytest-mock',
|
||||||
'requests',
|
'requests',
|
||||||
'requests-mock',
|
'requests-mock',
|
||||||
|
'webtest'
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,13 +13,34 @@ _global:
|
||||||
yoloswag:
|
yoloswag:
|
||||||
vendor: dummy
|
vendor: dummy
|
||||||
help: Dummy Yoloswag Password
|
help: Dummy Yoloswag Password
|
||||||
|
ldap:
|
||||||
|
vendor: ldap
|
||||||
|
help: admin_password
|
||||||
|
parameters:
|
||||||
|
uri: ldap://localhost:8080
|
||||||
|
base_dn: dc=yunohost,dc=org
|
||||||
|
user_rdn: cn=admin,dc=yunohost,dc=org
|
||||||
|
arguments:
|
||||||
|
-v:
|
||||||
|
full: --version
|
||||||
|
help: Display Yoloswag versions
|
||||||
|
action: callback
|
||||||
|
callback:
|
||||||
|
method: test.src.testauth.yoloswag_version
|
||||||
|
return: true
|
||||||
|
-w:
|
||||||
|
full: --wersion
|
||||||
|
help: Not existing function
|
||||||
|
action: callback
|
||||||
|
callback:
|
||||||
|
method: test.src.testauth.not_existing_function
|
||||||
|
return: true
|
||||||
|
|
||||||
#############################
|
#############################
|
||||||
# Test Actions #
|
# Test Actions #
|
||||||
#############################
|
#############################
|
||||||
testauth:
|
testauth:
|
||||||
actions:
|
actions:
|
||||||
|
|
||||||
none:
|
none:
|
||||||
api: GET /test-auth/none
|
api: GET /test-auth/none
|
||||||
configuration:
|
configuration:
|
||||||
|
@ -27,16 +48,76 @@ testauth:
|
||||||
|
|
||||||
default:
|
default:
|
||||||
api: GET /test-auth/default
|
api: GET /test-auth/default
|
||||||
configuration:
|
|
||||||
authenticate: all
|
|
||||||
authenticator: default
|
|
||||||
|
|
||||||
# only-api:
|
only-api:
|
||||||
# api: GET /test-auth/only-api
|
api: GET /test-auth/only-api
|
||||||
# configuration:
|
configuration:
|
||||||
# authenticate: api
|
authenticate:
|
||||||
#
|
- api
|
||||||
|
|
||||||
|
only-cli:
|
||||||
|
api: GET /test-auth/only-cli
|
||||||
|
configuration:
|
||||||
|
authenticate:
|
||||||
|
- cli
|
||||||
|
|
||||||
other-profile:
|
other-profile:
|
||||||
api: GET /test-auth/other-profile
|
api: GET /test-auth/other-profile
|
||||||
|
configuration:
|
||||||
|
authenticate:
|
||||||
|
- all
|
||||||
|
authenticator: yoloswag
|
||||||
|
|
||||||
|
ldap:
|
||||||
|
api: GET /test-auth/ldap
|
||||||
|
configuration:
|
||||||
|
authenticate:
|
||||||
|
- all
|
||||||
|
authenticator: ldap
|
||||||
|
|
||||||
|
with_arg:
|
||||||
|
api: GET /test-auth/with_arg/<super_arg>
|
||||||
|
arguments:
|
||||||
|
super_arg:
|
||||||
|
help: Super Arg
|
||||||
|
|
||||||
|
with_extra_str_only:
|
||||||
|
api: GET /test-auth/with_extra_str_only/<only_a_str>
|
||||||
|
arguments:
|
||||||
|
only_a_str:
|
||||||
|
help: Only a String
|
||||||
|
extra:
|
||||||
|
pattern:
|
||||||
|
- !!str ^[a-zA-Z]
|
||||||
|
- "pattern_only_a_str"
|
||||||
|
|
||||||
|
with_type_int:
|
||||||
|
api: GET /test-auth/with_type_int/<only_an_int>
|
||||||
|
arguments:
|
||||||
|
only_an_int:
|
||||||
|
help: Only an Int
|
||||||
|
type: int
|
||||||
|
|
||||||
|
subcategories:
|
||||||
|
subcat:
|
||||||
|
actions:
|
||||||
|
none:
|
||||||
|
api: GET /test-auth/subcat/none
|
||||||
|
configuration:
|
||||||
|
authenticate: false
|
||||||
|
|
||||||
|
default:
|
||||||
|
api: GET /test-auth/subcat/default
|
||||||
|
|
||||||
|
post:
|
||||||
|
api: POST /test-auth/subcat/post
|
||||||
|
configuration:
|
||||||
|
authenticate:
|
||||||
|
- all
|
||||||
|
authenticator: default
|
||||||
|
|
||||||
|
|
||||||
|
other-profile:
|
||||||
|
api: GET /test-auth/subcat/other-profile
|
||||||
configuration:
|
configuration:
|
||||||
authenticator: yoloswag
|
authenticator: yoloswag
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
"""Pytest fixtures for testing."""
|
"""Pytest fixtures for testing."""
|
||||||
|
|
||||||
|
import toml
|
||||||
|
import yaml
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from src.ldap_server import LDAPServer
|
||||||
|
|
||||||
|
|
||||||
def patch_init(moulinette):
|
def patch_init(moulinette):
|
||||||
"""Configure moulinette to use the YunoHost namespace."""
|
"""Configure moulinette to use the YunoHost namespace."""
|
||||||
|
@ -53,7 +57,7 @@ def patch_logging(moulinette):
|
||||||
"format": "%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s" # noqa
|
"format": "%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s" # noqa
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"filters": {"action": {"()": "moulinette.utils.log.ActionFilter",},},
|
"filters": {"action": {"()": "moulinette.utils.log.ActionFilter"}},
|
||||||
"handlers": {
|
"handlers": {
|
||||||
"api": {
|
"api": {
|
||||||
"level": level,
|
"level": level,
|
||||||
|
@ -66,17 +70,21 @@ def patch_logging(moulinette):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"loggers": {
|
"loggers": {
|
||||||
"moulinette": {"level": level, "handlers": [], "propagate": True,},
|
"moulinette": {"level": level, "handlers": [], "propagate": True},
|
||||||
"moulinette.interface": {
|
"moulinette.interface": {
|
||||||
"level": level,
|
"level": level,
|
||||||
"handlers": handlers,
|
"handlers": handlers,
|
||||||
"propagate": False,
|
"propagate": False,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"root": {"level": level, "handlers": root_handlers,},
|
"root": {"level": level, "handlers": root_handlers},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def patch_lock(moulinette):
|
||||||
|
moulinette.core.MoulinetteLock.base_lockfile = "moulinette_%s.lock"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
def moulinette(tmp_path_factory):
|
def moulinette(tmp_path_factory):
|
||||||
import moulinette
|
import moulinette
|
||||||
|
@ -96,6 +104,7 @@ def moulinette(tmp_path_factory):
|
||||||
|
|
||||||
patch_init(moulinette)
|
patch_init(moulinette)
|
||||||
patch_translate(moulinette)
|
patch_translate(moulinette)
|
||||||
|
patch_lock(moulinette)
|
||||||
logging = patch_logging(moulinette)
|
logging = patch_logging(moulinette)
|
||||||
|
|
||||||
moulinette.init(logging_config=logging, _from_source=False)
|
moulinette.init(logging_config=logging, _from_source=False)
|
||||||
|
@ -125,6 +134,33 @@ def moulinette_webapi(moulinette):
|
||||||
return TestApp(moulinette_webapi._app)
|
return TestApp(moulinette_webapi._app)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def moulinette_cli(moulinette, mocker):
|
||||||
|
# Dirty hack needed, otherwise cookies ain't reused between request .. not
|
||||||
|
# sure why :|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
|
parser.add_argument(
|
||||||
|
"--debug",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Log and print debug messages",
|
||||||
|
)
|
||||||
|
mocker.patch("os.isatty", return_value=True)
|
||||||
|
moulinette_cli = moulinette.core.init_interface(
|
||||||
|
"cli",
|
||||||
|
actionsmap={
|
||||||
|
"namespaces": ["moulitest"],
|
||||||
|
"use_cache": False,
|
||||||
|
"parser_kwargs": {"top_parser": parser},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
mocker.stopall()
|
||||||
|
|
||||||
|
return moulinette_cli
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_file(tmp_path):
|
def test_file(tmp_path):
|
||||||
test_text = "foo\nbar\n"
|
test_text = "foo\nbar\n"
|
||||||
|
@ -141,6 +177,41 @@ def test_json(tmp_path):
|
||||||
return test_file
|
return test_file
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_yaml(tmp_path):
|
||||||
|
test_yaml = yaml.dump({"foo": "bar"})
|
||||||
|
test_file = tmp_path / "test.txt"
|
||||||
|
test_file.write_bytes(test_yaml)
|
||||||
|
return test_file
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_toml(tmp_path):
|
||||||
|
test_toml = toml.dumps({"foo": "bar"})
|
||||||
|
test_file = tmp_path / "test.txt"
|
||||||
|
test_file.write_bytes(str(test_toml))
|
||||||
|
return test_file
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_ldif(tmp_path):
|
||||||
|
test_file = tmp_path / "test.txt"
|
||||||
|
from ldif import LDIFWriter
|
||||||
|
|
||||||
|
writer = LDIFWriter(open(str(test_file), "wb"))
|
||||||
|
|
||||||
|
writer.unparse(
|
||||||
|
"mail=alice@example.com",
|
||||||
|
{
|
||||||
|
"cn": ["Alice Alison"],
|
||||||
|
"mail": ["alice@example.com"],
|
||||||
|
"objectclass": ["top", "person"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return test_file
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def user():
|
def user():
|
||||||
return os.getlogin()
|
return os.getlogin()
|
||||||
|
@ -149,3 +220,11 @@ def user():
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_url():
|
def test_url():
|
||||||
return "https://some.test.url/yolo.txt"
|
return "https://some.test.url/yolo.txt"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def ldap_server():
|
||||||
|
server = LDAPServer()
|
||||||
|
server.start()
|
||||||
|
yield server
|
||||||
|
server.stop()
|
||||||
|
|
0
test/ldap_files/__init__.py
Normal file
0
test/ldap_files/__init__.py
Normal file
84
test/ldap_files/ldap_scheme.yml
Normal file
84
test/ldap_files/ldap_scheme.yml
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
parents:
|
||||||
|
ou=users:
|
||||||
|
ou: users
|
||||||
|
objectClass:
|
||||||
|
- organizationalUnit
|
||||||
|
- top
|
||||||
|
|
||||||
|
ou=domains:
|
||||||
|
ou: domains
|
||||||
|
objectClass:
|
||||||
|
- organizationalUnit
|
||||||
|
- top
|
||||||
|
|
||||||
|
ou=apps:
|
||||||
|
ou: apps
|
||||||
|
objectClass:
|
||||||
|
- organizationalUnit
|
||||||
|
- top
|
||||||
|
|
||||||
|
ou=permission:
|
||||||
|
ou: permission
|
||||||
|
objectClass:
|
||||||
|
- organizationalUnit
|
||||||
|
- top
|
||||||
|
|
||||||
|
ou=groups:
|
||||||
|
ou: groups
|
||||||
|
objectClass:
|
||||||
|
- organizationalUnit
|
||||||
|
- top
|
||||||
|
|
||||||
|
ou=sudo:
|
||||||
|
ou: sudo
|
||||||
|
objectClass:
|
||||||
|
- organizationalUnit
|
||||||
|
- top
|
||||||
|
|
||||||
|
children:
|
||||||
|
cn=admin,ou=sudo:
|
||||||
|
cn: admin
|
||||||
|
sudoUser: admin
|
||||||
|
sudoHost: ALL
|
||||||
|
sudoCommand: ALL
|
||||||
|
sudoOption: "!authenticate"
|
||||||
|
objectClass:
|
||||||
|
- sudoRole
|
||||||
|
- top
|
||||||
|
cn=admins,ou=groups:
|
||||||
|
cn: admins
|
||||||
|
gidNumber: "4001"
|
||||||
|
memberUid: admin
|
||||||
|
objectClass:
|
||||||
|
- posixGroup
|
||||||
|
- top
|
||||||
|
cn=all_users,ou=groups:
|
||||||
|
cn: all_users
|
||||||
|
gidNumber: "4002"
|
||||||
|
objectClass:
|
||||||
|
- posixGroup
|
||||||
|
- groupOfNamesYnh
|
||||||
|
cn=visitors,ou=groups:
|
||||||
|
cn: visitors
|
||||||
|
gidNumber: "4003"
|
||||||
|
objectClass:
|
||||||
|
- posixGroup
|
||||||
|
- groupOfNamesYnh
|
||||||
|
|
||||||
|
depends_children:
|
||||||
|
cn=mail.main,ou=permission:
|
||||||
|
cn: mail.main
|
||||||
|
gidNumber: "5001"
|
||||||
|
objectClass:
|
||||||
|
- posixGroup
|
||||||
|
- permissionYnh
|
||||||
|
groupPermission:
|
||||||
|
- "cn=all_users,ou=groups,dc=yunohost,dc=org"
|
||||||
|
cn=xmpp.main,ou=permission:
|
||||||
|
cn: xmpp.main
|
||||||
|
gidNumber: "5002"
|
||||||
|
objectClass:
|
||||||
|
- posixGroup
|
||||||
|
- permissionYnh
|
||||||
|
groupPermission:
|
||||||
|
- "cn=all_users,ou=groups,dc=yunohost,dc=org"
|
610
test/ldap_files/schema/core.schema
Normal file
610
test/ldap_files/schema/core.schema
Normal file
|
@ -0,0 +1,610 @@
|
||||||
|
# OpenLDAP Core schema
|
||||||
|
# $OpenLDAP$
|
||||||
|
## This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||||
|
##
|
||||||
|
## Copyright 1998-2019 The OpenLDAP Foundation.
|
||||||
|
## All rights reserved.
|
||||||
|
##
|
||||||
|
## Redistribution and use in source and binary forms, with or without
|
||||||
|
## modification, are permitted only as authorized by the OpenLDAP
|
||||||
|
## Public License.
|
||||||
|
##
|
||||||
|
## A copy of this license is available in the file LICENSE in the
|
||||||
|
## top-level directory of the distribution or, alternatively, at
|
||||||
|
## <http://www.OpenLDAP.org/license.html>.
|
||||||
|
#
|
||||||
|
## Portions Copyright (C) The Internet Society (1997-2006).
|
||||||
|
## All Rights Reserved.
|
||||||
|
##
|
||||||
|
## This document and translations of it may be copied and furnished to
|
||||||
|
## others, and derivative works that comment on or otherwise explain it
|
||||||
|
## or assist in its implementation may be prepared, copied, published
|
||||||
|
## and distributed, in whole or in part, without restriction of any
|
||||||
|
## kind, provided that the above copyright notice and this paragraph are
|
||||||
|
## included on all such copies and derivative works. However, this
|
||||||
|
## document itself may not be modified in any way, such as by removing
|
||||||
|
## the copyright notice or references to the Internet Society or other
|
||||||
|
## Internet organizations, except as needed for the purpose of
|
||||||
|
## developing Internet standards in which case the procedures for
|
||||||
|
## copyrights defined in the Internet Standards process must be
|
||||||
|
## followed, or as required to translate it into languages other than
|
||||||
|
## English.
|
||||||
|
##
|
||||||
|
## The limited permissions granted above are perpetual and will not be
|
||||||
|
## revoked by the Internet Society or its successors or assigns.
|
||||||
|
##
|
||||||
|
## This document and the information contained herein is provided on an
|
||||||
|
## "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
|
||||||
|
## TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
|
||||||
|
## BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
|
||||||
|
## HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
|
||||||
|
## MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Includes LDAPv3 schema items from:
|
||||||
|
# RFC 2252/2256 (LDAPv3)
|
||||||
|
#
|
||||||
|
# Select standard track schema items:
|
||||||
|
# RFC 1274 (uid/dc)
|
||||||
|
# RFC 2079 (URI)
|
||||||
|
# RFC 2247 (dc/dcObject)
|
||||||
|
# RFC 2587 (PKI)
|
||||||
|
# RFC 2589 (Dynamic Directory Services)
|
||||||
|
# RFC 4524 (associatedDomain)
|
||||||
|
#
|
||||||
|
# Select informational schema items:
|
||||||
|
# RFC 2377 (uidObject)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Standard attribute types from RFC 2256
|
||||||
|
#
|
||||||
|
|
||||||
|
# system schema
|
||||||
|
#attributetype ( 2.5.4.0 NAME 'objectClass'
|
||||||
|
# DESC 'RFC2256: object classes of the entity'
|
||||||
|
# EQUALITY objectIdentifierMatch
|
||||||
|
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
|
||||||
|
|
||||||
|
# system schema
|
||||||
|
#attributetype ( 2.5.4.1 NAME ( 'aliasedObjectName' 'aliasedEntryName' )
|
||||||
|
# DESC 'RFC2256: name of aliased object'
|
||||||
|
# EQUALITY distinguishedNameMatch
|
||||||
|
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.2 NAME 'knowledgeInformation'
|
||||||
|
DESC 'RFC2256: knowledge information'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )
|
||||||
|
|
||||||
|
# system schema
|
||||||
|
#attributetype ( 2.5.4.3 NAME ( 'cn' 'commonName' )
|
||||||
|
# DESC 'RFC2256: common name(s) for which the entity is known by'
|
||||||
|
# SUP name )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.4 NAME ( 'sn' 'surname' )
|
||||||
|
DESC 'RFC2256: last (family) name(s) for which the entity is known by'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.5 NAME 'serialNumber'
|
||||||
|
DESC 'RFC2256: serial number of the entity'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{64} )
|
||||||
|
|
||||||
|
# RFC 4519 definition ('countryName' in X.500 and RFC2256)
|
||||||
|
attributetype ( 2.5.4.6 NAME ( 'c' 'countryName' )
|
||||||
|
DESC 'RFC4519: two-letter ISO-3166 country code'
|
||||||
|
SUP name
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.11
|
||||||
|
SINGLE-VALUE )
|
||||||
|
|
||||||
|
#attributetype ( 2.5.4.6 NAME ( 'c' 'countryName' )
|
||||||
|
# DESC 'RFC2256: ISO-3166 country 2-letter code'
|
||||||
|
# SUP name SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.7 NAME ( 'l' 'localityName' )
|
||||||
|
DESC 'RFC2256: locality which this object resides in'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.8 NAME ( 'st' 'stateOrProvinceName' )
|
||||||
|
DESC 'RFC2256: state or province which this object resides in'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.9 NAME ( 'street' 'streetAddress' )
|
||||||
|
DESC 'RFC2256: street address of this object'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.10 NAME ( 'o' 'organizationName' )
|
||||||
|
DESC 'RFC2256: organization this object belongs to'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.11 NAME ( 'ou' 'organizationalUnitName' )
|
||||||
|
DESC 'RFC2256: organizational unit this object belongs to'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.12 NAME 'title'
|
||||||
|
DESC 'RFC2256: title associated with the entity'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
# system schema
|
||||||
|
#attributetype ( 2.5.4.13 NAME 'description'
|
||||||
|
# DESC 'RFC2256: descriptive information'
|
||||||
|
# EQUALITY caseIgnoreMatch
|
||||||
|
# SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )
|
||||||
|
|
||||||
|
# Deprecated by enhancedSearchGuide
|
||||||
|
attributetype ( 2.5.4.14 NAME 'searchGuide'
|
||||||
|
DESC 'RFC2256: search guide, deprecated by enhancedSearchGuide'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.25 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.15 NAME 'businessCategory'
|
||||||
|
DESC 'RFC2256: business category'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.16 NAME 'postalAddress'
|
||||||
|
DESC 'RFC2256: postal address'
|
||||||
|
EQUALITY caseIgnoreListMatch
|
||||||
|
SUBSTR caseIgnoreListSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.17 NAME 'postalCode'
|
||||||
|
DESC 'RFC2256: postal code'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{40} )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.18 NAME 'postOfficeBox'
|
||||||
|
DESC 'RFC2256: Post Office Box'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{40} )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.19 NAME 'physicalDeliveryOfficeName'
|
||||||
|
DESC 'RFC2256: Physical Delivery Office Name'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.20 NAME 'telephoneNumber'
|
||||||
|
DESC 'RFC2256: Telephone Number'
|
||||||
|
EQUALITY telephoneNumberMatch
|
||||||
|
SUBSTR telephoneNumberSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.50{32} )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.21 NAME 'telexNumber'
|
||||||
|
DESC 'RFC2256: Telex Number'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.52 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.22 NAME 'teletexTerminalIdentifier'
|
||||||
|
DESC 'RFC2256: Teletex Terminal Identifier'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.51 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.23 NAME ( 'facsimileTelephoneNumber' 'fax' )
|
||||||
|
DESC 'RFC2256: Facsimile (Fax) Telephone Number'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.22 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.24 NAME 'x121Address'
|
||||||
|
DESC 'RFC2256: X.121 Address'
|
||||||
|
EQUALITY numericStringMatch
|
||||||
|
SUBSTR numericStringSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{15} )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.25 NAME 'internationaliSDNNumber'
|
||||||
|
DESC 'RFC2256: international ISDN number'
|
||||||
|
EQUALITY numericStringMatch
|
||||||
|
SUBSTR numericStringSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{16} )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.26 NAME 'registeredAddress'
|
||||||
|
DESC 'RFC2256: registered postal address'
|
||||||
|
SUP postalAddress
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.27 NAME 'destinationIndicator'
|
||||||
|
DESC 'RFC2256: destination indicator'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{128} )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.28 NAME 'preferredDeliveryMethod'
|
||||||
|
DESC 'RFC2256: preferred delivery method'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.14
|
||||||
|
SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.29 NAME 'presentationAddress'
|
||||||
|
DESC 'RFC2256: presentation address'
|
||||||
|
EQUALITY presentationAddressMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.43
|
||||||
|
SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.30 NAME 'supportedApplicationContext'
|
||||||
|
DESC 'RFC2256: supported application context'
|
||||||
|
EQUALITY objectIdentifierMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.31 NAME 'member'
|
||||||
|
DESC 'RFC2256: member of a group'
|
||||||
|
SUP distinguishedName )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.32 NAME 'owner'
|
||||||
|
DESC 'RFC2256: owner (of the object)'
|
||||||
|
SUP distinguishedName )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.33 NAME 'roleOccupant'
|
||||||
|
DESC 'RFC2256: occupant of role'
|
||||||
|
SUP distinguishedName )
|
||||||
|
|
||||||
|
# system schema
|
||||||
|
#attributetype ( 2.5.4.34 NAME 'seeAlso'
|
||||||
|
# DESC 'RFC2256: DN of related object'
|
||||||
|
# SUP distinguishedName )
|
||||||
|
|
||||||
|
# system schema
|
||||||
|
#attributetype ( 2.5.4.35 NAME 'userPassword'
|
||||||
|
# DESC 'RFC2256/2307: password of user'
|
||||||
|
# EQUALITY octetStringMatch
|
||||||
|
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{128} )
|
||||||
|
|
||||||
|
# Must be transferred using ;binary
|
||||||
|
# with certificateExactMatch rule (per X.509)
|
||||||
|
attributetype ( 2.5.4.36 NAME 'userCertificate'
|
||||||
|
DESC 'RFC2256: X.509 user certificate, use ;binary'
|
||||||
|
EQUALITY certificateExactMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 )
|
||||||
|
|
||||||
|
# Must be transferred using ;binary
|
||||||
|
# with certificateExactMatch rule (per X.509)
|
||||||
|
attributetype ( 2.5.4.37 NAME 'cACertificate'
|
||||||
|
DESC 'RFC2256: X.509 CA certificate, use ;binary'
|
||||||
|
EQUALITY certificateExactMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 )
|
||||||
|
|
||||||
|
# Must be transferred using ;binary
|
||||||
|
attributetype ( 2.5.4.38 NAME 'authorityRevocationList'
|
||||||
|
DESC 'RFC2256: X.509 authority revocation list, use ;binary'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 )
|
||||||
|
|
||||||
|
# Must be transferred using ;binary
|
||||||
|
attributetype ( 2.5.4.39 NAME 'certificateRevocationList'
|
||||||
|
DESC 'RFC2256: X.509 certificate revocation list, use ;binary'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 )
|
||||||
|
|
||||||
|
# Must be stored and requested in the binary form
|
||||||
|
attributetype ( 2.5.4.40 NAME 'crossCertificatePair'
|
||||||
|
DESC 'RFC2256: X.509 cross certificate pair, use ;binary'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.10 )
|
||||||
|
|
||||||
|
# system schema
|
||||||
|
#attributetype ( 2.5.4.41 NAME 'name'
|
||||||
|
# EQUALITY caseIgnoreMatch
|
||||||
|
# SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.42 NAME ( 'givenName' 'gn' )
|
||||||
|
DESC 'RFC2256: first name(s) for which the entity is known by'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.43 NAME 'initials'
|
||||||
|
DESC 'RFC2256: initials of some or all of names, but not the surname(s).'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.44 NAME 'generationQualifier'
|
||||||
|
DESC 'RFC2256: name qualifier indicating a generation'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.45 NAME 'x500UniqueIdentifier'
|
||||||
|
DESC 'RFC2256: X.500 unique identifier'
|
||||||
|
EQUALITY bitStringMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.6 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.46 NAME 'dnQualifier'
|
||||||
|
DESC 'RFC2256: DN qualifier'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
ORDERING caseIgnoreOrderingMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.47 NAME 'enhancedSearchGuide'
|
||||||
|
DESC 'RFC2256: enhanced search guide'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.21 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.48 NAME 'protocolInformation'
|
||||||
|
DESC 'RFC2256: protocol information'
|
||||||
|
EQUALITY protocolInformationMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.42 )
|
||||||
|
|
||||||
|
# system schema
|
||||||
|
#attributetype ( 2.5.4.49 NAME 'distinguishedName'
|
||||||
|
# EQUALITY distinguishedNameMatch
|
||||||
|
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.50 NAME 'uniqueMember'
|
||||||
|
DESC 'RFC2256: unique member of a group'
|
||||||
|
EQUALITY uniqueMemberMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.34 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.51 NAME 'houseIdentifier'
|
||||||
|
DESC 'RFC2256: house identifier'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )
|
||||||
|
|
||||||
|
# Must be transferred using ;binary
|
||||||
|
attributetype ( 2.5.4.52 NAME 'supportedAlgorithms'
|
||||||
|
DESC 'RFC2256: supported algorithms'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.49 )
|
||||||
|
|
||||||
|
# Must be transferred using ;binary
|
||||||
|
attributetype ( 2.5.4.53 NAME 'deltaRevocationList'
|
||||||
|
DESC 'RFC2256: delta revocation list; use ;binary'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.54 NAME 'dmdName'
|
||||||
|
DESC 'RFC2256: name of DMD'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
attributetype ( 2.5.4.65 NAME 'pseudonym'
|
||||||
|
DESC 'X.520(4th): pseudonym for the object'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
# Standard object classes from RFC2256
|
||||||
|
|
||||||
|
# system schema
|
||||||
|
#objectclass ( 2.5.6.0 NAME 'top'
|
||||||
|
# DESC 'RFC2256: top of the superclass chain'
|
||||||
|
# ABSTRACT
|
||||||
|
# MUST objectClass )
|
||||||
|
|
||||||
|
# system schema
|
||||||
|
#objectclass ( 2.5.6.1 NAME 'alias'
|
||||||
|
# DESC 'RFC2256: an alias'
|
||||||
|
# SUP top STRUCTURAL
|
||||||
|
# MUST aliasedObjectName )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.2 NAME 'country'
|
||||||
|
DESC 'RFC2256: a country'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST c
|
||||||
|
MAY ( searchGuide $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.3 NAME 'locality'
|
||||||
|
DESC 'RFC2256: a locality'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MAY ( street $ seeAlso $ searchGuide $ st $ l $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.4 NAME 'organization'
|
||||||
|
DESC 'RFC2256: an organization'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST o
|
||||||
|
MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $
|
||||||
|
x121Address $ registeredAddress $ destinationIndicator $
|
||||||
|
preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $
|
||||||
|
telephoneNumber $ internationaliSDNNumber $
|
||||||
|
facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $
|
||||||
|
postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.5 NAME 'organizationalUnit'
|
||||||
|
DESC 'RFC2256: an organizational unit'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ou
|
||||||
|
MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $
|
||||||
|
x121Address $ registeredAddress $ destinationIndicator $
|
||||||
|
preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $
|
||||||
|
telephoneNumber $ internationaliSDNNumber $
|
||||||
|
facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $
|
||||||
|
postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.6 NAME 'person'
|
||||||
|
DESC 'RFC2256: a person'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ( sn $ cn )
|
||||||
|
MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.7 NAME 'organizationalPerson'
|
||||||
|
DESC 'RFC2256: an organizational person'
|
||||||
|
SUP person STRUCTURAL
|
||||||
|
MAY ( title $ x121Address $ registeredAddress $ destinationIndicator $
|
||||||
|
preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $
|
||||||
|
telephoneNumber $ internationaliSDNNumber $
|
||||||
|
facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $
|
||||||
|
postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.8 NAME 'organizationalRole'
|
||||||
|
DESC 'RFC2256: an organizational role'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST cn
|
||||||
|
MAY ( x121Address $ registeredAddress $ destinationIndicator $
|
||||||
|
preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $
|
||||||
|
telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $
|
||||||
|
seeAlso $ roleOccupant $ preferredDeliveryMethod $ street $
|
||||||
|
postOfficeBox $ postalCode $ postalAddress $
|
||||||
|
physicalDeliveryOfficeName $ ou $ st $ l $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.9 NAME 'groupOfNames'
|
||||||
|
DESC 'RFC2256: a group of names (DNs)'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ( member $ cn )
|
||||||
|
MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.10 NAME 'residentialPerson'
|
||||||
|
DESC 'RFC2256: an residential person'
|
||||||
|
SUP person STRUCTURAL
|
||||||
|
MUST l
|
||||||
|
MAY ( businessCategory $ x121Address $ registeredAddress $
|
||||||
|
destinationIndicator $ preferredDeliveryMethod $ telexNumber $
|
||||||
|
teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $
|
||||||
|
facsimileTelephoneNumber $ preferredDeliveryMethod $ street $
|
||||||
|
postOfficeBox $ postalCode $ postalAddress $
|
||||||
|
physicalDeliveryOfficeName $ st $ l ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.11 NAME 'applicationProcess'
|
||||||
|
DESC 'RFC2256: an application process'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST cn
|
||||||
|
MAY ( seeAlso $ ou $ l $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.12 NAME 'applicationEntity'
|
||||||
|
DESC 'RFC2256: an application entity'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ( presentationAddress $ cn )
|
||||||
|
MAY ( supportedApplicationContext $ seeAlso $ ou $ o $ l $
|
||||||
|
description ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.13 NAME 'dSA'
|
||||||
|
DESC 'RFC2256: a directory system agent (a server)'
|
||||||
|
SUP applicationEntity STRUCTURAL
|
||||||
|
MAY knowledgeInformation )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.14 NAME 'device'
|
||||||
|
DESC 'RFC2256: a device'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST cn
|
||||||
|
MAY ( serialNumber $ seeAlso $ owner $ ou $ o $ l $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.15 NAME 'strongAuthenticationUser'
|
||||||
|
DESC 'RFC2256: a strong authentication user'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MUST userCertificate )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.16 NAME 'certificationAuthority'
|
||||||
|
DESC 'RFC2256: a certificate authority'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MUST ( authorityRevocationList $ certificateRevocationList $
|
||||||
|
cACertificate ) MAY crossCertificatePair )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.17 NAME 'groupOfUniqueNames'
|
||||||
|
DESC 'RFC2256: a group of unique names (DN and Unique Identifier)'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ( uniqueMember $ cn )
|
||||||
|
MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.18 NAME 'userSecurityInformation'
|
||||||
|
DESC 'RFC2256: a user security information'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MAY ( supportedAlgorithms ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.16.2 NAME 'certificationAuthority-V2'
|
||||||
|
SUP certificationAuthority
|
||||||
|
AUXILIARY MAY ( deltaRevocationList ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.19 NAME 'cRLDistributionPoint'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ( cn )
|
||||||
|
MAY ( certificateRevocationList $ authorityRevocationList $
|
||||||
|
deltaRevocationList ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.20 NAME 'dmd'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ( dmdName )
|
||||||
|
MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $
|
||||||
|
x121Address $ registeredAddress $ destinationIndicator $
|
||||||
|
preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $
|
||||||
|
telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $
|
||||||
|
street $ postOfficeBox $ postalCode $ postalAddress $
|
||||||
|
physicalDeliveryOfficeName $ st $ l $ description ) )
|
||||||
|
|
||||||
|
#
|
||||||
|
# Object Classes from RFC 2587
|
||||||
|
#
|
||||||
|
objectclass ( 2.5.6.21 NAME 'pkiUser'
|
||||||
|
DESC 'RFC2587: a PKI user'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MAY userCertificate )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.22 NAME 'pkiCA'
|
||||||
|
DESC 'RFC2587: PKI certificate authority'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MAY ( authorityRevocationList $ certificateRevocationList $
|
||||||
|
cACertificate $ crossCertificatePair ) )
|
||||||
|
|
||||||
|
objectclass ( 2.5.6.23 NAME 'deltaCRL'
|
||||||
|
DESC 'RFC2587: PKI user'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MAY deltaRevocationList )
|
||||||
|
|
||||||
|
#
|
||||||
|
# Standard Track URI label schema from RFC 2079
|
||||||
|
# system schema
|
||||||
|
#attributetype ( 1.3.6.1.4.1.250.1.57 NAME 'labeledURI'
|
||||||
|
# DESC 'RFC2079: Uniform Resource Identifier with optional label'
|
||||||
|
# EQUALITY caseExactMatch
|
||||||
|
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.4.1.250.3.15 NAME 'labeledURIObject'
|
||||||
|
DESC 'RFC2079: object that contains the URI attribute type'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MAY ( labeledURI ) )
|
||||||
|
|
||||||
|
#
|
||||||
|
# Derived from RFC 1274, but with new "short names"
|
||||||
|
#
|
||||||
|
#attributetype ( 0.9.2342.19200300.100.1.1
|
||||||
|
# NAME ( 'uid' 'userid' )
|
||||||
|
# DESC 'RFC1274: user identifier'
|
||||||
|
# EQUALITY caseIgnoreMatch
|
||||||
|
# SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
|
||||||
|
|
||||||
|
attributetype ( 0.9.2342.19200300.100.1.3
|
||||||
|
NAME ( 'mail' 'rfc822Mailbox' )
|
||||||
|
DESC 'RFC1274: RFC822 Mailbox'
|
||||||
|
EQUALITY caseIgnoreIA5Match
|
||||||
|
SUBSTR caseIgnoreIA5SubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
|
||||||
|
|
||||||
|
objectclass ( 0.9.2342.19200300.100.4.19 NAME 'simpleSecurityObject'
|
||||||
|
DESC 'RFC1274: simple security object'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MUST userPassword )
|
||||||
|
|
||||||
|
# RFC 1274 + RFC 2247
|
||||||
|
attributetype ( 0.9.2342.19200300.100.1.25
|
||||||
|
NAME ( 'dc' 'domainComponent' )
|
||||||
|
DESC 'RFC1274/2247: domain component'
|
||||||
|
EQUALITY caseIgnoreIA5Match
|
||||||
|
SUBSTR caseIgnoreIA5SubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
|
||||||
|
|
||||||
|
# RFC 2247
|
||||||
|
objectclass ( 1.3.6.1.4.1.1466.344 NAME 'dcObject'
|
||||||
|
DESC 'RFC2247: domain component object'
|
||||||
|
SUP top AUXILIARY MUST dc )
|
||||||
|
|
||||||
|
# RFC 2377
|
||||||
|
objectclass ( 1.3.6.1.1.3.1 NAME 'uidObject'
|
||||||
|
DESC 'RFC2377: uid object'
|
||||||
|
SUP top AUXILIARY MUST uid )
|
||||||
|
|
||||||
|
# RFC 4524
|
||||||
|
# The 'associatedDomain' attribute specifies DNS [RFC1034][RFC2181]
|
||||||
|
# host names [RFC1123] that are associated with an object. That is,
|
||||||
|
# values of this attribute should conform to the following ABNF:
|
||||||
|
#
|
||||||
|
# domain = root / label *( DOT label )
|
||||||
|
# root = SPACE
|
||||||
|
# label = LETDIG [ *61( LETDIG / HYPHEN ) LETDIG ]
|
||||||
|
# LETDIG = %x30-39 / %x41-5A / %x61-7A ; "0" - "9" / "A"-"Z" / "a"-"z"
|
||||||
|
# SPACE = %x20 ; space (" ")
|
||||||
|
# HYPHEN = %x2D ; hyphen ("-")
|
||||||
|
# DOT = %x2E ; period (".")
|
||||||
|
attributetype ( 0.9.2342.19200300.100.1.37
|
||||||
|
NAME 'associatedDomain'
|
||||||
|
DESC 'RFC1274: domain associated with object'
|
||||||
|
EQUALITY caseIgnoreIA5Match
|
||||||
|
SUBSTR caseIgnoreIA5SubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||||
|
|
||||||
|
# RFC 2459 -- deprecated in favor of 'mail' (in cosine.schema)
|
||||||
|
attributetype ( 1.2.840.113549.1.9.1
|
||||||
|
NAME ( 'email' 'emailAddress' 'pkcs9email' )
|
||||||
|
DESC 'RFC3280: legacy attribute for email addresses in DNs'
|
||||||
|
EQUALITY caseIgnoreIA5Match
|
||||||
|
SUBSTR caseIgnoreIA5SubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
|
||||||
|
|
2571
test/ldap_files/schema/cosine.schema
Normal file
2571
test/ldap_files/schema/cosine.schema
Normal file
File diff suppressed because it is too large
Load diff
155
test/ldap_files/schema/inetorgperson.schema
Normal file
155
test/ldap_files/schema/inetorgperson.schema
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
# inetorgperson.schema -- InetOrgPerson (RFC2798)
|
||||||
|
# $OpenLDAP$
|
||||||
|
## This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||||
|
##
|
||||||
|
## Copyright 1998-2019 The OpenLDAP Foundation.
|
||||||
|
## All rights reserved.
|
||||||
|
##
|
||||||
|
## Redistribution and use in source and binary forms, with or without
|
||||||
|
## modification, are permitted only as authorized by the OpenLDAP
|
||||||
|
## Public License.
|
||||||
|
##
|
||||||
|
## A copy of this license is available in the file LICENSE in the
|
||||||
|
## top-level directory of the distribution or, alternatively, at
|
||||||
|
## <http://www.OpenLDAP.org/license.html>.
|
||||||
|
#
|
||||||
|
# InetOrgPerson (RFC2798)
|
||||||
|
#
|
||||||
|
# Depends upon
|
||||||
|
# Definition of an X.500 Attribute Type and an Object Class to Hold
|
||||||
|
# Uniform Resource Identifiers (URIs) [RFC2079]
|
||||||
|
# (core.schema)
|
||||||
|
#
|
||||||
|
# A Summary of the X.500(96) User Schema for use with LDAPv3 [RFC2256]
|
||||||
|
# (core.schema)
|
||||||
|
#
|
||||||
|
# The COSINE and Internet X.500 Schema [RFC1274] (cosine.schema)
|
||||||
|
|
||||||
|
# carLicense
|
||||||
|
# This multivalued field is used to record the values of the license or
|
||||||
|
# registration plate associated with an individual.
|
||||||
|
attributetype ( 2.16.840.1.113730.3.1.1
|
||||||
|
NAME 'carLicense'
|
||||||
|
DESC 'RFC2798: vehicle license or registration plate'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
|
||||||
|
|
||||||
|
# departmentNumber
|
||||||
|
# Code for department to which a person belongs. This can also be
|
||||||
|
# strictly numeric (e.g., 1234) or alphanumeric (e.g., ABC/123).
|
||||||
|
attributetype ( 2.16.840.1.113730.3.1.2
|
||||||
|
NAME 'departmentNumber'
|
||||||
|
DESC 'RFC2798: identifies a department within an organization'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
|
||||||
|
|
||||||
|
# displayName
|
||||||
|
# When displaying an entry, especially within a one-line summary list, it
|
||||||
|
# is useful to be able to identify a name to be used. Since other attri-
|
||||||
|
# bute types such as 'cn' are multivalued, an additional attribute type is
|
||||||
|
# needed. Display name is defined for this purpose.
|
||||||
|
attributetype ( 2.16.840.1.113730.3.1.241
|
||||||
|
NAME 'displayName'
|
||||||
|
DESC 'RFC2798: preferred name to be used when displaying entries'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||||
|
SINGLE-VALUE )
|
||||||
|
|
||||||
|
# employeeNumber
|
||||||
|
# Numeric or alphanumeric identifier assigned to a person, typically based
|
||||||
|
# on order of hire or association with an organization. Single valued.
|
||||||
|
attributetype ( 2.16.840.1.113730.3.1.3
|
||||||
|
NAME 'employeeNumber'
|
||||||
|
DESC 'RFC2798: numerically identifies an employee within an organization'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||||
|
SINGLE-VALUE )
|
||||||
|
|
||||||
|
# employeeType
|
||||||
|
# Used to identify the employer to employee relationship. Typical values
|
||||||
|
# used will be "Contractor", "Employee", "Intern", "Temp", "External", and
|
||||||
|
# "Unknown" but any value may be used.
|
||||||
|
attributetype ( 2.16.840.1.113730.3.1.4
|
||||||
|
NAME 'employeeType'
|
||||||
|
DESC 'RFC2798: type of employment for a person'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
|
||||||
|
|
||||||
|
# jpegPhoto
|
||||||
|
# Used to store one or more images of a person using the JPEG File
|
||||||
|
# Interchange Format [JFIF].
|
||||||
|
# Note that the jpegPhoto attribute type was defined for use in the
|
||||||
|
# Internet X.500 pilots but no referencable definition for it could be
|
||||||
|
# located.
|
||||||
|
attributetype ( 0.9.2342.19200300.100.1.60
|
||||||
|
NAME 'jpegPhoto'
|
||||||
|
DESC 'RFC2798: a JPEG image'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 )
|
||||||
|
|
||||||
|
# preferredLanguage
|
||||||
|
# Used to indicate an individual's preferred written or spoken
|
||||||
|
# language. This is useful for international correspondence or human-
|
||||||
|
# computer interaction. Values for this attribute type MUST conform to
|
||||||
|
# the definition of the Accept-Language header field defined in
|
||||||
|
# [RFC2068] with one exception: the sequence "Accept-Language" ":"
|
||||||
|
# should be omitted. This is a single valued attribute type.
|
||||||
|
attributetype ( 2.16.840.1.113730.3.1.39
|
||||||
|
NAME 'preferredLanguage'
|
||||||
|
DESC 'RFC2798: preferred written or spoken language for a person'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||||
|
SINGLE-VALUE )
|
||||||
|
|
||||||
|
# userSMIMECertificate
|
||||||
|
# A PKCS#7 [RFC2315] SignedData, where the content that is signed is
|
||||||
|
# ignored by consumers of userSMIMECertificate values. It is
|
||||||
|
# recommended that values have a `contentType' of data with an absent
|
||||||
|
# `content' field. Values of this attribute contain a person's entire
|
||||||
|
# certificate chain and an smimeCapabilities field [RFC2633] that at a
|
||||||
|
# minimum describes their SMIME algorithm capabilities. Values for
|
||||||
|
# this attribute are to be stored and requested in binary form, as
|
||||||
|
# 'userSMIMECertificate;binary'. If available, this attribute is
|
||||||
|
# preferred over the userCertificate attribute for S/MIME applications.
|
||||||
|
## OpenLDAP note: ";binary" transfer should NOT be used as syntax is binary
|
||||||
|
attributetype ( 2.16.840.1.113730.3.1.40
|
||||||
|
NAME 'userSMIMECertificate'
|
||||||
|
DESC 'RFC2798: PKCS#7 SignedData used to support S/MIME'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 )
|
||||||
|
|
||||||
|
# userPKCS12
|
||||||
|
# PKCS #12 [PKCS12] provides a format for exchange of personal identity
|
||||||
|
# information. When such information is stored in a directory service,
|
||||||
|
# the userPKCS12 attribute should be used. This attribute is to be stored
|
||||||
|
# and requested in binary form, as 'userPKCS12;binary'. The attribute
|
||||||
|
# values are PFX PDUs stored as binary data.
|
||||||
|
## OpenLDAP note: ";binary" transfer should NOT be used as syntax is binary
|
||||||
|
attributetype ( 2.16.840.1.113730.3.1.216
|
||||||
|
NAME 'userPKCS12'
|
||||||
|
DESC 'RFC2798: personal identity information, a PKCS #12 PFX'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 )
|
||||||
|
|
||||||
|
|
||||||
|
# inetOrgPerson
|
||||||
|
# The inetOrgPerson represents people who are associated with an
|
||||||
|
# organization in some way. It is a structural class and is derived
|
||||||
|
# from the organizationalPerson which is defined in X.521 [X521].
|
||||||
|
objectclass ( 2.16.840.1.113730.3.2.2
|
||||||
|
NAME 'inetOrgPerson'
|
||||||
|
DESC 'RFC2798: Internet Organizational Person'
|
||||||
|
SUP organizationalPerson
|
||||||
|
STRUCTURAL
|
||||||
|
MAY (
|
||||||
|
audio $ businessCategory $ carLicense $ departmentNumber $
|
||||||
|
displayName $ employeeNumber $ employeeType $ givenName $
|
||||||
|
homePhone $ homePostalAddress $ initials $ jpegPhoto $
|
||||||
|
labeledURI $ mail $ manager $ mobile $ o $ pager $
|
||||||
|
photo $ roomNumber $ secretary $ uid $ userCertificate $
|
||||||
|
x500uniqueIdentifier $ preferredLanguage $
|
||||||
|
userSMIMECertificate $ userPKCS12 )
|
||||||
|
)
|
88
test/ldap_files/schema/mailserver.schema
Normal file
88
test/ldap_files/schema/mailserver.schema
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
## LDAP Schema Yunohost EMAIL
|
||||||
|
## Version 0.1
|
||||||
|
## Adrien Beudin
|
||||||
|
|
||||||
|
# Attributes
|
||||||
|
attributetype ( 1.3.6.1.4.1.40328.1.20.2.1
|
||||||
|
NAME 'maildrop'
|
||||||
|
DESC 'Mail addresses where mails are forwarded -- ie forwards'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{512})
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.40328.1.20.2.2
|
||||||
|
NAME 'mailalias'
|
||||||
|
DESC 'Mail addresses accepted by this account -- ie aliases'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{512})
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.40328.1.20.2.3
|
||||||
|
NAME 'mailenable'
|
||||||
|
DESC 'Mail Account validity'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{8})
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.40328.1.20.2.4
|
||||||
|
NAME 'mailbox'
|
||||||
|
DESC 'Mailbox path where mails are delivered'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{512})
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.40328.1.20.2.5
|
||||||
|
NAME 'virtualdomain'
|
||||||
|
DESC 'A mail domain name'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{512})
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.40328.1.20.2.6
|
||||||
|
NAME 'virtualdomaindescription'
|
||||||
|
DESC 'Virtual domain description'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{512})
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.40328.1.20.2.7
|
||||||
|
NAME 'mailuserquota'
|
||||||
|
DESC 'Mailbox quota for a user'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{16} SINGLE-VALUE )
|
||||||
|
|
||||||
|
# Mail Account Objectclass
|
||||||
|
objectclass ( 1.3.6.1.4.1.40328.1.1.2.1
|
||||||
|
NAME 'mailAccount'
|
||||||
|
DESC 'Mail Account'
|
||||||
|
SUP top
|
||||||
|
AUXILIARY
|
||||||
|
MUST (
|
||||||
|
mail
|
||||||
|
)
|
||||||
|
MAY (
|
||||||
|
mailalias $ maildrop $ mailenable $ mailbox $ mailuserquota
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mail Domain Objectclass
|
||||||
|
objectclass ( 1.3.6.1.4.1.40328.1.1.2.2
|
||||||
|
NAME 'mailDomain'
|
||||||
|
DESC 'Domain mail entry'
|
||||||
|
SUP top
|
||||||
|
STRUCTURAL
|
||||||
|
MUST (
|
||||||
|
virtualdomain
|
||||||
|
)
|
||||||
|
MAY (
|
||||||
|
virtualdomaindescription $ mailuserquota
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mail Group Objectclass
|
||||||
|
objectclass ( 1.3.6.1.4.1.40328.1.1.2.3
|
||||||
|
NAME 'mailGroup' SUP top AUXILIARY
|
||||||
|
DESC 'Mail Group'
|
||||||
|
MUST ( mail )
|
||||||
|
)
|
237
test/ldap_files/schema/nis.schema
Normal file
237
test/ldap_files/schema/nis.schema
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
# $OpenLDAP$
|
||||||
|
## This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||||
|
##
|
||||||
|
## Copyright 1998-2019 The OpenLDAP Foundation.
|
||||||
|
## All rights reserved.
|
||||||
|
##
|
||||||
|
## Redistribution and use in source and binary forms, with or without
|
||||||
|
## modification, are permitted only as authorized by the OpenLDAP
|
||||||
|
## Public License.
|
||||||
|
##
|
||||||
|
## A copy of this license is available in the file LICENSE in the
|
||||||
|
## top-level directory of the distribution or, alternatively, at
|
||||||
|
## <http://www.OpenLDAP.org/license.html>.
|
||||||
|
|
||||||
|
# Definitions from RFC2307 (Experimental)
|
||||||
|
# An Approach for Using LDAP as a Network Information Service
|
||||||
|
|
||||||
|
# Depends upon core.schema and cosine.schema
|
||||||
|
|
||||||
|
# Note: The definitions in RFC2307 are given in syntaxes closely related
|
||||||
|
# to those in RFC2252, however, some liberties are taken that are not
|
||||||
|
# supported by RFC2252. This file has been written following RFC2252
|
||||||
|
# strictly.
|
||||||
|
|
||||||
|
# OID Base is iso(1) org(3) dod(6) internet(1) directory(1) nisSchema(1).
|
||||||
|
# i.e. nisSchema in RFC2307 is 1.3.6.1.1.1
|
||||||
|
#
|
||||||
|
# Syntaxes are under 1.3.6.1.1.1.0 (two new syntaxes are defined)
|
||||||
|
# validaters for these syntaxes are incomplete, they only
|
||||||
|
# implement printable string validation (which is good as the
|
||||||
|
# common use of these syntaxes violates the specification).
|
||||||
|
# Attribute types are under 1.3.6.1.1.1.1
|
||||||
|
# Object classes are under 1.3.6.1.1.1.2
|
||||||
|
|
||||||
|
# Attribute Type Definitions
|
||||||
|
|
||||||
|
# builtin
|
||||||
|
#attributetype ( 1.3.6.1.1.1.1.0 NAME 'uidNumber'
|
||||||
|
# DESC 'An integer uniquely identifying a user in an administrative domain'
|
||||||
|
# EQUALITY integerMatch
|
||||||
|
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
|
||||||
|
|
||||||
|
# builtin
|
||||||
|
#attributetype ( 1.3.6.1.1.1.1.1 NAME 'gidNumber'
|
||||||
|
# DESC 'An integer uniquely identifying a group in an administrative domain'
|
||||||
|
# EQUALITY integerMatch
|
||||||
|
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.2 NAME 'gecos'
|
||||||
|
DESC 'The GECOS field; the common name'
|
||||||
|
EQUALITY caseIgnoreIA5Match
|
||||||
|
SUBSTR caseIgnoreIA5SubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.3 NAME 'homeDirectory'
|
||||||
|
DESC 'The absolute path to the home directory'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.4 NAME 'loginShell'
|
||||||
|
DESC 'The path to the login shell'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange'
|
||||||
|
EQUALITY integerMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.6 NAME 'shadowMin'
|
||||||
|
EQUALITY integerMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.7 NAME 'shadowMax'
|
||||||
|
EQUALITY integerMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.8 NAME 'shadowWarning'
|
||||||
|
EQUALITY integerMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.9 NAME 'shadowInactive'
|
||||||
|
EQUALITY integerMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.10 NAME 'shadowExpire'
|
||||||
|
EQUALITY integerMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.11 NAME 'shadowFlag'
|
||||||
|
EQUALITY integerMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.12 NAME 'memberUid'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SUBSTR caseExactIA5SubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SUBSTR caseExactIA5SubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple'
|
||||||
|
DESC 'Netgroup triple'
|
||||||
|
SYNTAX 1.3.6.1.1.1.0.0 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.15 NAME 'ipServicePort'
|
||||||
|
EQUALITY integerMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber'
|
||||||
|
EQUALITY integerMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber'
|
||||||
|
EQUALITY integerMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber'
|
||||||
|
DESC 'IP address'
|
||||||
|
EQUALITY caseIgnoreIA5Match
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber'
|
||||||
|
DESC 'IP network'
|
||||||
|
EQUALITY caseIgnoreIA5Match
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber'
|
||||||
|
DESC 'IP netmask'
|
||||||
|
EQUALITY caseIgnoreIA5Match
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} SINGLE-VALUE )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.22 NAME 'macAddress'
|
||||||
|
DESC 'MAC address'
|
||||||
|
EQUALITY caseIgnoreIA5Match
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.23 NAME 'bootParameter'
|
||||||
|
DESC 'rpc.bootparamd parameter'
|
||||||
|
SYNTAX 1.3.6.1.1.1.0.1 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.24 NAME 'bootFile'
|
||||||
|
DESC 'Boot image name'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.26 NAME 'nisMapName'
|
||||||
|
SUP name )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SUBSTR caseExactIA5SubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{1024} SINGLE-VALUE )
|
||||||
|
|
||||||
|
# Object Class Definitions
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.0 NAME 'posixAccount'
|
||||||
|
DESC 'Abstraction of an account with POSIX attributes'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
|
||||||
|
MAY ( userPassword $ loginShell $ gecos $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.1 NAME 'shadowAccount'
|
||||||
|
DESC 'Additional attributes for shadow passwords'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MUST uid
|
||||||
|
MAY ( userPassword $ shadowLastChange $ shadowMin $
|
||||||
|
shadowMax $ shadowWarning $ shadowInactive $
|
||||||
|
shadowExpire $ shadowFlag $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.2 NAME 'posixGroup'
|
||||||
|
DESC 'Abstraction of a group of accounts'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ( cn $ gidNumber )
|
||||||
|
MAY ( userPassword $ memberUid $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.3 NAME 'ipService'
|
||||||
|
DESC 'Abstraction an Internet Protocol service'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ( cn $ ipServicePort $ ipServiceProtocol )
|
||||||
|
MAY ( description ) )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.4 NAME 'ipProtocol'
|
||||||
|
DESC 'Abstraction of an IP protocol'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ( cn $ ipProtocolNumber $ description )
|
||||||
|
MAY description )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.5 NAME 'oncRpc'
|
||||||
|
DESC 'Abstraction of an ONC/RPC binding'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ( cn $ oncRpcNumber $ description )
|
||||||
|
MAY description )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.6 NAME 'ipHost'
|
||||||
|
DESC 'Abstraction of a host, an IP device'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MUST ( cn $ ipHostNumber )
|
||||||
|
MAY ( l $ description $ manager ) )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.7 NAME 'ipNetwork'
|
||||||
|
DESC 'Abstraction of an IP network'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ( cn $ ipNetworkNumber )
|
||||||
|
MAY ( ipNetmaskNumber $ l $ description $ manager ) )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup'
|
||||||
|
DESC 'Abstraction of a netgroup'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST cn
|
||||||
|
MAY ( nisNetgroupTriple $ memberNisNetgroup $ description ) )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.9 NAME 'nisMap'
|
||||||
|
DESC 'A generic abstraction of a NIS map'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST nisMapName
|
||||||
|
MAY description )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.10 NAME 'nisObject'
|
||||||
|
DESC 'An entry in a NIS map'
|
||||||
|
SUP top STRUCTURAL
|
||||||
|
MUST ( cn $ nisMapEntry $ nisMapName )
|
||||||
|
MAY description )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.11 NAME 'ieee802Device'
|
||||||
|
DESC 'A device with a MAC address'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MAY macAddress )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.1.1.2.12 NAME 'bootableDevice'
|
||||||
|
DESC 'A device with boot parameters'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MAY ( bootFile $ bootParameter ) )
|
76
test/ldap_files/schema/sudo.schema
Normal file
76
test/ldap_files/schema/sudo.schema
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#
|
||||||
|
# OpenLDAP schema file for Sudo
|
||||||
|
# Save as /etc/openldap/schema/sudo.schema
|
||||||
|
#
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.15953.9.1.1
|
||||||
|
NAME 'sudoUser'
|
||||||
|
DESC 'User(s) who may run sudo'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SUBSTR caseExactIA5SubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.15953.9.1.2
|
||||||
|
NAME 'sudoHost'
|
||||||
|
DESC 'Host(s) who may run sudo'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SUBSTR caseExactIA5SubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.15953.9.1.3
|
||||||
|
NAME 'sudoCommand'
|
||||||
|
DESC 'Command(s) to be executed by sudo'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.15953.9.1.4
|
||||||
|
NAME 'sudoRunAs'
|
||||||
|
DESC 'User(s) impersonated by sudo (deprecated)'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.15953.9.1.5
|
||||||
|
NAME 'sudoOption'
|
||||||
|
DESC 'Options(s) followed by sudo'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.15953.9.1.6
|
||||||
|
NAME 'sudoRunAsUser'
|
||||||
|
DESC 'User(s) impersonated by sudo'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.15953.9.1.7
|
||||||
|
NAME 'sudoRunAsGroup'
|
||||||
|
DESC 'Group(s) impersonated by sudo'
|
||||||
|
EQUALITY caseExactIA5Match
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.15953.9.1.8
|
||||||
|
NAME 'sudoNotBefore'
|
||||||
|
DESC 'Start of time interval for which the entry is valid'
|
||||||
|
EQUALITY generalizedTimeMatch
|
||||||
|
ORDERING generalizedTimeOrderingMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )
|
||||||
|
|
||||||
|
attributetype ( 1.3.6.1.4.1.15953.9.1.9
|
||||||
|
NAME 'sudoNotAfter'
|
||||||
|
DESC 'End of time interval for which the entry is valid'
|
||||||
|
EQUALITY generalizedTimeMatch
|
||||||
|
ORDERING generalizedTimeOrderingMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )
|
||||||
|
|
||||||
|
attributeTypes ( 1.3.6.1.4.1.15953.9.1.10
|
||||||
|
NAME 'sudoOrder'
|
||||||
|
DESC 'an integer to order the sudoRole entries'
|
||||||
|
EQUALITY integerMatch
|
||||||
|
ORDERING integerOrderingMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )
|
||||||
|
|
||||||
|
objectclass ( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL
|
||||||
|
DESC 'Sudoer Entries'
|
||||||
|
MUST ( cn )
|
||||||
|
MAY ( sudoUser $ sudoHost $ sudoCommand $ sudoRunAs $ sudoRunAsUser $ sudoRunAsGroup $ sudoOption $ sudoOrder $ sudoNotBefore $ sudoNotAfter $
|
||||||
|
description )
|
||||||
|
)
|
33
test/ldap_files/schema/yunohost.schema
Normal file
33
test/ldap_files/schema/yunohost.schema
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#dn: cn=yunohost,cn=schema,cn=config
|
||||||
|
#objectClass: olcSchemaConfig
|
||||||
|
#cn: yunohost
|
||||||
|
# ATTRIBUTES
|
||||||
|
# For Permission
|
||||||
|
attributetype ( 1.3.6.1.4.1.17953.9.1.1 NAME 'permission'
|
||||||
|
DESC 'Yunohost permission on user and group side'
|
||||||
|
SUP distinguishedName )
|
||||||
|
attributetype ( 1.3.6.1.4.1.17953.9.1.2 NAME 'groupPermission'
|
||||||
|
DESC 'Yunohost permission for a group on permission side'
|
||||||
|
SUP distinguishedName )
|
||||||
|
attributetype ( 1.3.6.1.4.1.17953.9.1.3 NAME 'inheritPermission'
|
||||||
|
DESC 'Yunohost permission for user on permission side'
|
||||||
|
SUP distinguishedName )
|
||||||
|
attributetype ( 1.3.6.1.4.1.17953.9.1.4 NAME 'URL'
|
||||||
|
DESC 'Yunohost application URL'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
|
||||||
|
# OBJECTCLASS
|
||||||
|
# For Applications
|
||||||
|
objectclass ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh'
|
||||||
|
DESC 'Yunohost user group'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MAY ( member $ businessCategory $ seeAlso $ owner $ ou $ o $ permission ) )
|
||||||
|
objectclass ( 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 ) )
|
||||||
|
# For User
|
||||||
|
objectclass ( 1.3.6.1.4.1.17953.9.2.3 NAME 'userPermissionYnh'
|
||||||
|
DESC 'a Yunohost application'
|
||||||
|
SUP top AUXILIARY
|
||||||
|
MAY ( permission ) )
|
94
test/ldap_files/slapd.conf.template
Normal file
94
test/ldap_files/slapd.conf.template
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
serverID %(serverid)s
|
||||||
|
moduleload back_%(database)s
|
||||||
|
moduleload memberof
|
||||||
|
%(include_directives)s
|
||||||
|
loglevel %(loglevel)s
|
||||||
|
#allow bind_v2
|
||||||
|
database %(database)s
|
||||||
|
directory "%(directory)s"
|
||||||
|
suffix "%(suffix)s"
|
||||||
|
rootdn "%(rootdn)s"
|
||||||
|
rootpw "%(rootpw)s"
|
||||||
|
TLSCACertificateFile "%(cafile)s"
|
||||||
|
TLSCertificateFile "%(servercert)s"
|
||||||
|
TLSCertificateKeyFile "%(serverkey)s"
|
||||||
|
authz-regexp
|
||||||
|
"gidnumber=%(root_gid)s\\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth"
|
||||||
|
"%(rootdn)s"
|
||||||
|
|
||||||
|
index objectClass eq
|
||||||
|
index uid,sudoUser eq,sub
|
||||||
|
index entryCSN,entryUUID eq
|
||||||
|
index cn,mail eq
|
||||||
|
index gidNumber,uidNumber eq
|
||||||
|
index member,memberUid,uniqueMember eq
|
||||||
|
index virtualdomain eq
|
||||||
|
|
||||||
|
# The userPassword by default can be changed
|
||||||
|
# by the entry owning it if they are authenticated.
|
||||||
|
# Others should not be able to see it, except the
|
||||||
|
# admin entry below
|
||||||
|
# These access lines apply to database #1 only
|
||||||
|
access to attrs=userPassword,shadowLastChange
|
||||||
|
by dn="cn=admin,dc=yunohost,dc=org" write
|
||||||
|
by dn.exact="gidNumber=%(root_gid)s+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth" write
|
||||||
|
by anonymous auth
|
||||||
|
by self write
|
||||||
|
by * none
|
||||||
|
|
||||||
|
# Personnal information can be changed by the entry
|
||||||
|
# owning it if they are authenticated.
|
||||||
|
# Others should be able to see it.
|
||||||
|
access to attrs=cn,gecos,givenName,mail,maildrop,displayName,sn
|
||||||
|
by dn="cn=admin,dc=yunohost,dc=org" write
|
||||||
|
by dn.exact="gidNumber=%(root_gid)s+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth" write
|
||||||
|
by self write
|
||||||
|
by * read
|
||||||
|
|
||||||
|
# Ensure read access to the base for things like
|
||||||
|
# supportedSASLMechanisms. Without this you may
|
||||||
|
# have problems with SASL not knowing what
|
||||||
|
# mechanisms are available and the like.
|
||||||
|
# Note that this is covered by the 'access to *'
|
||||||
|
# ACL below too but if you change that as people
|
||||||
|
# are wont to do you'll still need this if you
|
||||||
|
# want SASL (and possible ldap_files things) to work
|
||||||
|
# happily.
|
||||||
|
access to dn.base="" by * read
|
||||||
|
|
||||||
|
# The admin dn has full write access, everyone else
|
||||||
|
# can read everything.
|
||||||
|
access to *
|
||||||
|
by dn="cn=admin,dc=yunohost,dc=org" write
|
||||||
|
by dn.exact="gidNumber=%(root_gid)s+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth" write
|
||||||
|
by group/groupOfNames/Member="cn=admin,ou=groups,dc=yunohost,dc=org" write
|
||||||
|
by * read
|
||||||
|
|
||||||
|
# Configure Memberof Overlay (used for Yunohost permission)
|
||||||
|
|
||||||
|
# Link user <-> group
|
||||||
|
#dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config
|
||||||
|
overlay memberof
|
||||||
|
memberof-group-oc groupOfNamesYnh
|
||||||
|
memberof-member-ad member
|
||||||
|
memberof-memberof-ad memberOf
|
||||||
|
memberof-dangling error
|
||||||
|
memberof-refint TRUE
|
||||||
|
|
||||||
|
# Link permission <-> groupes
|
||||||
|
#dn: olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config
|
||||||
|
overlay memberof
|
||||||
|
memberof-group-oc permissionYnh
|
||||||
|
memberof-member-ad groupPermission
|
||||||
|
memberof-memberof-ad permission
|
||||||
|
memberof-dangling error
|
||||||
|
memberof-refint TRUE
|
||||||
|
|
||||||
|
# Link permission <-> user
|
||||||
|
#dn: olcOverlay={2}memberof,olcDatabase={1}mdb,cn=config
|
||||||
|
overlay memberof
|
||||||
|
memberof-group-oc permissionYnh
|
||||||
|
memberof-member-ad inheritPermission
|
||||||
|
memberof-memberof-ad permission
|
||||||
|
memberof-dangling error
|
||||||
|
memberof-refint TRUE
|
205
test/ldap_files/tests.ldif
Normal file
205
test/ldap_files/tests.ldif
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
dn: dc=yunohost,dc=org
|
||||||
|
dc: yunohost
|
||||||
|
o: yunohost.org
|
||||||
|
objectclass: top
|
||||||
|
objectclass: dcObject
|
||||||
|
objectclass: organization
|
||||||
|
|
||||||
|
dn: cn=admin,dc=yunohost,dc=org
|
||||||
|
objectClass: simpleSecurityObject
|
||||||
|
objectClass: organizationalRole
|
||||||
|
cn: admin
|
||||||
|
userPassword: yunohost
|
||||||
|
|
||||||
|
#dn: ou=people,dc=yunohost,dc=org
|
||||||
|
#objectClass: organizationalUnit
|
||||||
|
#ou: people
|
||||||
|
#
|
||||||
|
#dn: ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#objectClass: organizationalUnit
|
||||||
|
#ou: moregroups
|
||||||
|
#
|
||||||
|
#dn: ou=mirror_groups,dc=yunohost,dc=org
|
||||||
|
#objectClass: organizationalUnit
|
||||||
|
#ou: mirror_groups
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#dn: uid=alice,ou=people,dc=yunohost,dc=org
|
||||||
|
#objectClass: person
|
||||||
|
#objectClass: organizationalPerson
|
||||||
|
#objectClass: inetOrgPerson
|
||||||
|
#objectClass: posixAccount
|
||||||
|
#cn: alice
|
||||||
|
#uid: alice
|
||||||
|
#userPassword: password
|
||||||
|
#uidNumber: 1000
|
||||||
|
#gidNumber: 1000
|
||||||
|
#givenName: Alice
|
||||||
|
#sn: Adams
|
||||||
|
#homeDirectory: /home/alice
|
||||||
|
#
|
||||||
|
#dn: uid=bob,ou=people,dc=yunohost,dc=org
|
||||||
|
#objectClass: person
|
||||||
|
#objectClass: organizationalPerson
|
||||||
|
#objectClass: inetOrgPerson
|
||||||
|
#objectClass: posixAccount
|
||||||
|
#cn: bob
|
||||||
|
#uid: bob
|
||||||
|
#userPassword: password
|
||||||
|
#uidNumber: 1001
|
||||||
|
#gidNumber: 50
|
||||||
|
#givenName: Robert
|
||||||
|
#sn: Barker
|
||||||
|
#homeDirectory: /home/bob
|
||||||
|
#
|
||||||
|
#dn: uid=dreßler,ou=people,dc=yunohost,dc=org
|
||||||
|
#objectClass: person
|
||||||
|
#objectClass: organizationalPerson
|
||||||
|
#objectClass: inetOrgPerson
|
||||||
|
#objectClass: posixAccount
|
||||||
|
#cn: dreßler
|
||||||
|
#uid: dreßler
|
||||||
|
#userPassword: password
|
||||||
|
#uidNumber: 1002
|
||||||
|
#gidNumber: 50
|
||||||
|
#givenName: Wolfgang
|
||||||
|
#sn: Dreßler
|
||||||
|
#homeDirectory: /home/dressler
|
||||||
|
#
|
||||||
|
#dn: uid=nobody,ou=people,dc=yunohost,dc=org
|
||||||
|
#objectClass: person
|
||||||
|
#objectClass: organizationalPerson
|
||||||
|
#objectClass: inetOrgPerson
|
||||||
|
#objectClass: posixAccount
|
||||||
|
#cn: nobody
|
||||||
|
#uid: nobody
|
||||||
|
#userPassword: password
|
||||||
|
#uidNumber: 1003
|
||||||
|
#gidNumber: 50
|
||||||
|
#sn: nobody
|
||||||
|
#homeDirectory: /home/nobody
|
||||||
|
#
|
||||||
|
#dn: uid=nonposix,ou=people,dc=yunohost,dc=org
|
||||||
|
#objectClass: person
|
||||||
|
#objectClass: organizationalPerson
|
||||||
|
#objectClass: inetOrgPerson
|
||||||
|
#cn: nonposix
|
||||||
|
#uid: nonposix
|
||||||
|
#userPassword: password
|
||||||
|
#sn: nonposix
|
||||||
|
#
|
||||||
|
#
|
||||||
|
## posixGroup objects
|
||||||
|
#dn: cn=active_px,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#objectClass: posixGroup
|
||||||
|
#cn: active_px
|
||||||
|
#gidNumber: 1000
|
||||||
|
#memberUid: nonposix
|
||||||
|
#
|
||||||
|
#dn: cn=staff_px,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#objectClass: posixGroup
|
||||||
|
#cn: staff_px
|
||||||
|
#gidNumber: 1001
|
||||||
|
#memberUid: alice
|
||||||
|
#memberUid: nonposix
|
||||||
|
#
|
||||||
|
#dn: cn=superuser_px,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#objectClass: posixGroup
|
||||||
|
#cn: superuser_px
|
||||||
|
#gidNumber: 1002
|
||||||
|
#memberUid: alice
|
||||||
|
#memberUid: nonposix
|
||||||
|
#
|
||||||
|
#
|
||||||
|
## groupOfNames groups
|
||||||
|
#dn: cn=empty_gon,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#cn: empty_gon
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member:
|
||||||
|
#
|
||||||
|
#dn: cn=active_gon,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#cn: active_gon
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: uid=alice,ou=people,dc=yunohost,dc=org
|
||||||
|
#
|
||||||
|
#dn: cn=staff_gon,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#cn: staff_gon
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: uid=alice,ou=people,dc=yunohost,dc=org
|
||||||
|
#
|
||||||
|
#dn: cn=superuser_gon,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#cn: superuser_gon
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: uid=alice,ou=people,dc=yunohost,dc=org
|
||||||
|
#
|
||||||
|
#dn: cn=other_gon,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#cn: other_gon
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: uid=bob,ou=people,dc=yunohost,dc=org
|
||||||
|
#
|
||||||
|
#
|
||||||
|
## groupOfNames objects for LDAPGroupQuery testing
|
||||||
|
#dn: ou=query_groups,dc=yunohost,dc=org
|
||||||
|
#objectClass: organizationalUnit
|
||||||
|
#ou: query_groups
|
||||||
|
#
|
||||||
|
#dn: cn=alice_gon,ou=query_groups,dc=yunohost,dc=org
|
||||||
|
#cn: alice_gon
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: uid=alice,ou=people,dc=yunohost,dc=org
|
||||||
|
#
|
||||||
|
#dn: cn=mutual_gon,ou=query_groups,dc=yunohost,dc=org
|
||||||
|
#cn: mutual_gon
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: uid=alice,ou=people,dc=yunohost,dc=org
|
||||||
|
#member: uid=bob,ou=people,dc=yunohost,dc=org
|
||||||
|
#
|
||||||
|
#dn: cn=bob_gon,ou=query_groups,dc=yunohost,dc=org
|
||||||
|
#cn: bob_gon
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: uid=bob,ou=people,dc=yunohost,dc=org
|
||||||
|
#
|
||||||
|
#dn: cn=dreßler_gon,ou=query_groups,dc=yunohost,dc=org
|
||||||
|
#cn: dreßler_gon
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: uid=dreßler,ou=people,dc=yunohost,dc=org
|
||||||
|
#
|
||||||
|
#
|
||||||
|
## groupOfNames objects for selective group mirroring.
|
||||||
|
#dn: cn=mirror1,ou=mirror_groups,dc=yunohost,dc=org
|
||||||
|
#cn: mirror1
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: uid=alice,ou=people,dc=yunohost,dc=org
|
||||||
|
#
|
||||||
|
#dn: cn=mirror2,ou=mirror_groups,dc=yunohost,dc=org
|
||||||
|
#cn: mirror2
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member:
|
||||||
|
#
|
||||||
|
#dn: cn=mirror3,ou=mirror_groups,dc=yunohost,dc=org
|
||||||
|
#cn: mirror3
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: uid=alice,ou=people,dc=yunohost,dc=org
|
||||||
|
#
|
||||||
|
#dn: cn=mirror4,ou=mirror_groups,dc=yunohost,dc=org
|
||||||
|
#cn: mirror4
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member:
|
||||||
|
#
|
||||||
|
#
|
||||||
|
## Nested groups with a circular reference
|
||||||
|
#dn: cn=parent_gon,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#cn: parent_gon
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: cn=nested_gon,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#
|
||||||
|
#dn: CN=nested_gon,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#cn: nested_gon
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: uid=alice,ou=people,dc=yunohost,dc=org
|
||||||
|
#member: cn=circular_gon,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#
|
||||||
|
#dn: cn=circular_gon,ou=moregroups,dc=yunohost,dc=org
|
||||||
|
#cn: circular_gon
|
||||||
|
#objectClass: groupOfNames
|
||||||
|
#member: cn=parent_gon,ou=moregroups,dc=yunohost,dc=org
|
|
@ -1,3 +1,5 @@
|
||||||
{
|
{
|
||||||
"foo": "bar"
|
"foo": "bar",
|
||||||
|
"Dummy Password": "Dummy Password",
|
||||||
|
"Dummy Yoloswag Password": "Dummy Yoloswag Password"
|
||||||
}
|
}
|
||||||
|
|
104
test/src/ldap_server.py
Normal file
104
test/src/ldap_server.py
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
try:
|
||||||
|
import slapdtest
|
||||||
|
except ImportError:
|
||||||
|
import old_slapdtest as slapdtest
|
||||||
|
import os
|
||||||
|
from moulinette.authenticators import ldap as m_ldap
|
||||||
|
|
||||||
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPServer:
|
||||||
|
def __init__(self):
|
||||||
|
self.server_default = slapdtest.SlapdObject()
|
||||||
|
with open(os.path.join(HERE, "..", "ldap_files", "slapd.conf.template")) as f:
|
||||||
|
SLAPD_CONF_TEMPLATE = f.read()
|
||||||
|
self.server_default.slapd_conf_template = SLAPD_CONF_TEMPLATE
|
||||||
|
self.server_default.suffix = "dc=yunohost,dc=org"
|
||||||
|
self.server_default.root_cn = "admin"
|
||||||
|
self.server_default.SCHEMADIR = os.path.join(HERE, "..", "ldap_files", "schema")
|
||||||
|
self.server_default.openldap_schema_files = [
|
||||||
|
"core.schema",
|
||||||
|
"cosine.schema",
|
||||||
|
"nis.schema",
|
||||||
|
"inetorgperson.schema",
|
||||||
|
"sudo.schema",
|
||||||
|
"yunohost.schema",
|
||||||
|
"mailserver.schema",
|
||||||
|
]
|
||||||
|
self.server = None
|
||||||
|
self.uri = ""
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.server = self.server_default
|
||||||
|
self.server.start()
|
||||||
|
self.uri = self.server.ldapi_uri
|
||||||
|
with open(os.path.join(HERE, "..", "ldap_files", "tests.ldif")) as fp:
|
||||||
|
ldif = fp.read().decode("utf-8")
|
||||||
|
self.server.ldapadd(ldif)
|
||||||
|
self.tools_ldapinit()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self.server:
|
||||||
|
self.server.stop()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if self.server:
|
||||||
|
self.server.stop()
|
||||||
|
|
||||||
|
def tools_ldapinit(self):
|
||||||
|
"""
|
||||||
|
YunoHost LDAP initialization
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
with open(os.path.join(HERE, "..", "ldap_files", "ldap_scheme.yml")) as f:
|
||||||
|
ldap_map = yaml.load(f)
|
||||||
|
|
||||||
|
def _get_ldap_interface():
|
||||||
|
conf = {
|
||||||
|
"vendor": "ldap",
|
||||||
|
"name": "as-root",
|
||||||
|
"parameters": {
|
||||||
|
"uri": self.server.ldapi_uri,
|
||||||
|
"base_dn": "dc=yunohost,dc=org",
|
||||||
|
"user_rdn": "gidNumber=%s+uidNumber=%s,cn=peercred,cn=external,cn=auth"
|
||||||
|
% (os.getgid(), os.getuid()),
|
||||||
|
},
|
||||||
|
"extra": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
_ldap_interface = m_ldap.Authenticator(**conf)
|
||||||
|
|
||||||
|
return _ldap_interface
|
||||||
|
|
||||||
|
ldap_interface = _get_ldap_interface()
|
||||||
|
|
||||||
|
for rdn, attr_dict in ldap_map["parents"].items():
|
||||||
|
ldap_interface.add(rdn, attr_dict)
|
||||||
|
|
||||||
|
for rdn, attr_dict in ldap_map["children"].items():
|
||||||
|
ldap_interface.add(rdn, attr_dict)
|
||||||
|
|
||||||
|
for rdn, attr_dict in ldap_map["depends_children"].items():
|
||||||
|
ldap_interface.add(rdn, attr_dict)
|
||||||
|
|
||||||
|
admin_dict = {
|
||||||
|
"cn": ["admin"],
|
||||||
|
"uid": ["admin"],
|
||||||
|
"description": ["LDAP Administrator"],
|
||||||
|
"gidNumber": ["1007"],
|
||||||
|
"uidNumber": ["1007"],
|
||||||
|
"homeDirectory": ["/home/admin"],
|
||||||
|
"loginShell": ["/bin/bash"],
|
||||||
|
"objectClass": [
|
||||||
|
"organizationalRole",
|
||||||
|
"posixAccount",
|
||||||
|
"simpleSecurityObject",
|
||||||
|
],
|
||||||
|
"userPassword": ["yunohost"],
|
||||||
|
}
|
||||||
|
|
||||||
|
ldap_interface.update("cn=admin", admin_dict)
|
2
test/src/old_slapdtest/README
Normal file
2
test/src/old_slapdtest/README
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
I have adapted the code from https://github.com/python-ldap/python-ldap/tree/master/Lib/slapdtest since the version of python-ldap we use does not provide the import slapdtest.
|
||||||
|
This part will must be removed once we switch to python3
|
2
test/src/old_slapdtest/__init__.py
Normal file
2
test/src/old_slapdtest/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# flake8: noqa
|
||||||
|
from ._slapdtest import SlapdObject
|
653
test/src/old_slapdtest/_slapdtest.py
Normal file
653
test/src/old_slapdtest/_slapdtest.py
Normal file
|
@ -0,0 +1,653 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# flake8: noqa
|
||||||
|
"""
|
||||||
|
slapdtest - module for spawning test instances of OpenLDAP's slapd server
|
||||||
|
See https://www.python-ldap.org/ for details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
import logging
|
||||||
|
import atexit
|
||||||
|
from logging.handlers import SysLogHandler
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
# Switch off processing .ldaprc or ldap.conf before importing _ldap
|
||||||
|
os.environ["LDAPNOINIT"] = "1"
|
||||||
|
|
||||||
|
import ldap
|
||||||
|
from urllib import quote_plus
|
||||||
|
|
||||||
|
try:
|
||||||
|
from shutil import which
|
||||||
|
except ImportError:
|
||||||
|
# shutil.which() from Python 3.6
|
||||||
|
# "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||||||
|
# 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation;
|
||||||
|
# All Rights Reserved"
|
||||||
|
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
||||||
|
"""Given a command, mode, and a PATH string, return the path which
|
||||||
|
conforms to the given mode on the PATH, or None if there is no such
|
||||||
|
file.
|
||||||
|
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
|
||||||
|
of os.environ.get("PATH"), or can be overridden with a custom search
|
||||||
|
path.
|
||||||
|
"""
|
||||||
|
# Check that a given file can be accessed with the correct mode.
|
||||||
|
# Additionally check that `file` is not a directory, as on Windows
|
||||||
|
# directories pass the os.access check.
|
||||||
|
def _access_check(fn, mode):
|
||||||
|
return os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn)
|
||||||
|
|
||||||
|
# If we're given a path with a directory part, look it up directly rather
|
||||||
|
# than referring to PATH directories. This includes checking relative to the
|
||||||
|
# current directory, e.g. ./script
|
||||||
|
if os.path.dirname(cmd):
|
||||||
|
if _access_check(cmd, mode):
|
||||||
|
return cmd
|
||||||
|
return None
|
||||||
|
|
||||||
|
if path is None:
|
||||||
|
path = os.environ.get("PATH", os.defpath)
|
||||||
|
if not path:
|
||||||
|
return None
|
||||||
|
path = path.split(os.pathsep)
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
# The current directory takes precedence on Windows.
|
||||||
|
if not os.curdir in path:
|
||||||
|
path.insert(0, os.curdir)
|
||||||
|
|
||||||
|
# PATHEXT is necessary to check on Windows.
|
||||||
|
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
|
||||||
|
# See if the given file matches any of the expected path extensions.
|
||||||
|
# This will allow us to short circuit when given "python.exe".
|
||||||
|
# If it does match, only test that one, otherwise we have to try
|
||||||
|
# others.
|
||||||
|
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
||||||
|
files = [cmd]
|
||||||
|
else:
|
||||||
|
files = [cmd + ext for ext in pathext]
|
||||||
|
else:
|
||||||
|
# On other platforms you don't have things like PATHEXT to tell you
|
||||||
|
# what file suffixes are executable, so just pass on cmd as-is.
|
||||||
|
files = [cmd]
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
for dir in path:
|
||||||
|
normdir = os.path.normcase(dir)
|
||||||
|
if not normdir in seen:
|
||||||
|
seen.add(normdir)
|
||||||
|
for thefile in files:
|
||||||
|
name = os.path.join(dir, thefile)
|
||||||
|
if _access_check(name, mode):
|
||||||
|
return name
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
# a template string for generating simple slapd.conf file
|
||||||
|
SLAPD_CONF_TEMPLATE = r"""
|
||||||
|
serverID %(serverid)s
|
||||||
|
moduleload back_%(database)s
|
||||||
|
%(include_directives)s
|
||||||
|
loglevel %(loglevel)s
|
||||||
|
allow bind_v2
|
||||||
|
authz-regexp
|
||||||
|
"gidnumber=%(root_gid)s\\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth"
|
||||||
|
"%(rootdn)s"
|
||||||
|
database %(database)s
|
||||||
|
directory "%(directory)s"
|
||||||
|
suffix "%(suffix)s"
|
||||||
|
rootdn "%(rootdn)s"
|
||||||
|
rootpw "%(rootpw)s"
|
||||||
|
TLSCACertificateFile "%(cafile)s"
|
||||||
|
TLSCertificateFile "%(servercert)s"
|
||||||
|
TLSCertificateKeyFile "%(serverkey)s"
|
||||||
|
# ignore missing client cert but fail with invalid client cert
|
||||||
|
TLSVerifyClient try
|
||||||
|
authz-regexp
|
||||||
|
"C=DE, O=python-ldap, OU=slapd-test, CN=([A-Za-z]+)"
|
||||||
|
"ldap://ou=people,dc=local???($1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
LOCALHOST = "127.0.0.1"
|
||||||
|
|
||||||
|
CI_DISABLED = set(os.environ.get("CI_DISABLED", "").split(":"))
|
||||||
|
if "LDAPI" in CI_DISABLED:
|
||||||
|
HAVE_LDAPI = False
|
||||||
|
else:
|
||||||
|
HAVE_LDAPI = hasattr(socket, "AF_UNIX")
|
||||||
|
|
||||||
|
|
||||||
|
def identity(test_item):
|
||||||
|
"""Identity decorator
|
||||||
|
"""
|
||||||
|
return test_item
|
||||||
|
|
||||||
|
|
||||||
|
def skip_unless_ci(reason, feature=None):
|
||||||
|
"""Skip test unless test case is executed on CI like Travis CI
|
||||||
|
"""
|
||||||
|
if not os.environ.get("CI", False):
|
||||||
|
return unittest.skip(reason)
|
||||||
|
elif feature in CI_DISABLED:
|
||||||
|
return unittest.skip(reason)
|
||||||
|
else:
|
||||||
|
# Don't skip on Travis
|
||||||
|
return identity
|
||||||
|
|
||||||
|
|
||||||
|
def requires_tls():
|
||||||
|
"""Decorator for TLS tests
|
||||||
|
Tests are not skipped on CI (e.g. Travis CI)
|
||||||
|
"""
|
||||||
|
if not ldap.TLS_AVAIL:
|
||||||
|
return skip_unless_ci("test needs ldap.TLS_AVAIL", feature="TLS")
|
||||||
|
else:
|
||||||
|
return identity
|
||||||
|
|
||||||
|
|
||||||
|
def requires_sasl():
|
||||||
|
if not ldap.SASL_AVAIL:
|
||||||
|
return skip_unless_ci("test needs ldap.SASL_AVAIL", feature="SASL")
|
||||||
|
else:
|
||||||
|
return identity
|
||||||
|
|
||||||
|
|
||||||
|
def requires_ldapi():
|
||||||
|
if not HAVE_LDAPI:
|
||||||
|
return skip_unless_ci("test needs ldapi support (AF_UNIX)", feature="LDAPI")
|
||||||
|
else:
|
||||||
|
return identity
|
||||||
|
|
||||||
|
|
||||||
|
def _add_sbin(path):
|
||||||
|
"""Add /sbin and related directories to a command search path"""
|
||||||
|
directories = path.split(os.pathsep)
|
||||||
|
if sys.platform != "win32":
|
||||||
|
for sbin in "/usr/local/sbin", "/sbin", "/usr/sbin":
|
||||||
|
if sbin not in directories:
|
||||||
|
directories.append(sbin)
|
||||||
|
return os.pathsep.join(directories)
|
||||||
|
|
||||||
|
|
||||||
|
def combined_logger(
|
||||||
|
log_name,
|
||||||
|
log_level=logging.WARN,
|
||||||
|
sys_log_format="%(levelname)s %(message)s",
|
||||||
|
console_log_format="%(asctime)s %(levelname)s %(message)s",
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Returns a combined SysLogHandler/StreamHandler logging instance
|
||||||
|
with formatters
|
||||||
|
"""
|
||||||
|
if "LOGLEVEL" in os.environ:
|
||||||
|
log_level = os.environ["LOGLEVEL"]
|
||||||
|
try:
|
||||||
|
log_level = int(log_level)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
# for writing to syslog
|
||||||
|
new_logger = logging.getLogger(log_name)
|
||||||
|
if sys_log_format and os.path.exists("/dev/log"):
|
||||||
|
my_syslog_formatter = logging.Formatter(
|
||||||
|
fmt=" ".join((log_name, sys_log_format))
|
||||||
|
)
|
||||||
|
my_syslog_handler = logging.handlers.SysLogHandler(
|
||||||
|
address="/dev/log", facility=SysLogHandler.LOG_DAEMON,
|
||||||
|
)
|
||||||
|
my_syslog_handler.setFormatter(my_syslog_formatter)
|
||||||
|
new_logger.addHandler(my_syslog_handler)
|
||||||
|
if console_log_format:
|
||||||
|
my_stream_formatter = logging.Formatter(fmt=console_log_format)
|
||||||
|
my_stream_handler = logging.StreamHandler()
|
||||||
|
my_stream_handler.setFormatter(my_stream_formatter)
|
||||||
|
new_logger.addHandler(my_stream_handler)
|
||||||
|
new_logger.setLevel(log_level)
|
||||||
|
return new_logger # end of combined_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class SlapdObject(object):
|
||||||
|
"""
|
||||||
|
Controller class for a slapd instance, OpenLDAP's server.
|
||||||
|
This class creates a temporary data store for slapd, runs it
|
||||||
|
listening on a private Unix domain socket and TCP port,
|
||||||
|
and initializes it with a top-level entry and the root user.
|
||||||
|
When a reference to an instance of this class is lost, the slapd
|
||||||
|
server is shut down.
|
||||||
|
An instance can be used as a context manager. When exiting the context
|
||||||
|
manager, the slapd server is shut down and the temporary data store is
|
||||||
|
removed.
|
||||||
|
.. versionchanged:: 3.1
|
||||||
|
Added context manager functionality
|
||||||
|
"""
|
||||||
|
|
||||||
|
slapd_conf_template = SLAPD_CONF_TEMPLATE
|
||||||
|
database = "mdb"
|
||||||
|
suffix = "dc=slapd-test,dc=python-ldap,dc=org"
|
||||||
|
root_cn = "Manager"
|
||||||
|
root_pw = "password"
|
||||||
|
slapd_loglevel = "stats stats2"
|
||||||
|
local_host = "127.0.0.1"
|
||||||
|
testrunsubdirs = ("schema",)
|
||||||
|
openldap_schema_files = ("core.schema",)
|
||||||
|
|
||||||
|
TMPDIR = os.environ.get("TMP", os.getcwd())
|
||||||
|
if "SCHEMA" in os.environ:
|
||||||
|
SCHEMADIR = os.environ["SCHEMA"]
|
||||||
|
elif os.path.isdir("/etc/openldap/schema"):
|
||||||
|
SCHEMADIR = "/etc/openldap/schema"
|
||||||
|
elif os.path.isdir("/etc/ldap/schema"):
|
||||||
|
SCHEMADIR = "/etc/ldap/schema"
|
||||||
|
else:
|
||||||
|
SCHEMADIR = None
|
||||||
|
|
||||||
|
BIN_PATH = os.environ.get("BIN", os.environ.get("PATH", os.defpath))
|
||||||
|
SBIN_PATH = os.environ.get("SBIN", _add_sbin(BIN_PATH))
|
||||||
|
|
||||||
|
# time in secs to wait before trying to access slapd via LDAP (again)
|
||||||
|
_start_sleep = 1.5
|
||||||
|
|
||||||
|
# create loggers once, multiple calls mess up refleak tests
|
||||||
|
_log = combined_logger("python-ldap-test")
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._proc = None
|
||||||
|
self._port = self._avail_tcp_port()
|
||||||
|
self.server_id = self._port % 4096
|
||||||
|
self.testrundir = os.path.join(self.TMPDIR, "python-ldap-test-%d" % self._port)
|
||||||
|
self._schema_prefix = os.path.join(self.testrundir, "schema")
|
||||||
|
self._slapd_conf = os.path.join(self.testrundir, "slapd.conf")
|
||||||
|
self._db_directory = os.path.join(self.testrundir, "openldap-data")
|
||||||
|
self.ldap_uri = "ldap://%s:%d/" % (LOCALHOST, self._port)
|
||||||
|
if HAVE_LDAPI:
|
||||||
|
ldapi_path = os.path.join(self.testrundir, "ldapi")
|
||||||
|
self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path)
|
||||||
|
self.default_ldap_uri = self.ldapi_uri
|
||||||
|
# use SASL/EXTERNAL via LDAPI when invoking OpenLDAP CLI tools
|
||||||
|
self.cli_sasl_external = ldap.SASL_AVAIL
|
||||||
|
else:
|
||||||
|
self.ldapi_uri = None
|
||||||
|
self.default_ldap_uri = self.ldap_uri
|
||||||
|
# Use simple bind via LDAP uri
|
||||||
|
self.cli_sasl_external = False
|
||||||
|
|
||||||
|
self._find_commands()
|
||||||
|
|
||||||
|
if self.SCHEMADIR is None:
|
||||||
|
raise ValueError("SCHEMADIR is None, ldap schemas are missing.")
|
||||||
|
|
||||||
|
# TLS certs
|
||||||
|
self.cafile = os.path.join(HERE, "certs/ca.pem")
|
||||||
|
self.servercert = os.path.join(HERE, "certs/server.pem")
|
||||||
|
self.serverkey = os.path.join(HERE, "certs/server.key")
|
||||||
|
self.clientcert = os.path.join(HERE, "certs/client.pem")
|
||||||
|
self.clientkey = os.path.join(HERE, "certs/client.key")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root_dn(self):
|
||||||
|
return "cn={self.root_cn},{self.suffix}".format(self=self)
|
||||||
|
|
||||||
|
def _find_commands(self):
|
||||||
|
self.PATH_LDAPADD = self._find_command("ldapadd")
|
||||||
|
self.PATH_LDAPDELETE = self._find_command("ldapdelete")
|
||||||
|
self.PATH_LDAPMODIFY = self._find_command("ldapmodify")
|
||||||
|
self.PATH_LDAPWHOAMI = self._find_command("ldapwhoami")
|
||||||
|
|
||||||
|
self.PATH_SLAPD = os.environ.get("SLAPD", None)
|
||||||
|
if not self.PATH_SLAPD:
|
||||||
|
self.PATH_SLAPD = self._find_command("slapd", in_sbin=True)
|
||||||
|
|
||||||
|
def _find_command(self, cmd, in_sbin=False):
|
||||||
|
if in_sbin:
|
||||||
|
path = self.SBIN_PATH
|
||||||
|
var_name = "SBIN"
|
||||||
|
else:
|
||||||
|
path = self.BIN_PATH
|
||||||
|
var_name = "BIN"
|
||||||
|
command = which(cmd, path=path)
|
||||||
|
if command is None:
|
||||||
|
raise ValueError(
|
||||||
|
"Command '{}' not found. Set the {} environment variable to "
|
||||||
|
"override slapdtest's search path.".format(cmd, var_name)
|
||||||
|
)
|
||||||
|
return command
|
||||||
|
|
||||||
|
def setup_rundir(self):
|
||||||
|
"""
|
||||||
|
creates rundir structure
|
||||||
|
for setting up a custom directory structure you have to override
|
||||||
|
this method
|
||||||
|
"""
|
||||||
|
os.mkdir(self.testrundir)
|
||||||
|
os.mkdir(self._db_directory)
|
||||||
|
self._create_sub_dirs(self.testrunsubdirs)
|
||||||
|
self._ln_schema_files(self.openldap_schema_files, self.SCHEMADIR)
|
||||||
|
|
||||||
|
def _cleanup_rundir(self):
|
||||||
|
"""
|
||||||
|
Recursively delete whole directory specified by `path'
|
||||||
|
"""
|
||||||
|
# cleanup_rundir() is called in atexit handler. Until Python 3.4,
|
||||||
|
# the rest of the world is already destroyed.
|
||||||
|
import os, os.path
|
||||||
|
|
||||||
|
if not os.path.exists(self.testrundir):
|
||||||
|
return
|
||||||
|
self._log.debug("clean-up %s", self.testrundir)
|
||||||
|
for dirpath, dirnames, filenames in os.walk(self.testrundir, topdown=False):
|
||||||
|
for filename in filenames:
|
||||||
|
self._log.debug("remove %s", os.path.join(dirpath, filename))
|
||||||
|
os.remove(os.path.join(dirpath, filename))
|
||||||
|
for dirname in dirnames:
|
||||||
|
self._log.debug("rmdir %s", os.path.join(dirpath, dirname))
|
||||||
|
os.rmdir(os.path.join(dirpath, dirname))
|
||||||
|
os.rmdir(self.testrundir)
|
||||||
|
self._log.info("cleaned-up %s", self.testrundir)
|
||||||
|
|
||||||
|
def _avail_tcp_port(self):
|
||||||
|
"""
|
||||||
|
find an available port for TCP connection
|
||||||
|
"""
|
||||||
|
sock = socket.socket()
|
||||||
|
try:
|
||||||
|
sock.bind((self.local_host, 0))
|
||||||
|
port = sock.getsockname()[1]
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
self._log.info("Found available port %d", port)
|
||||||
|
return port
|
||||||
|
|
||||||
|
def gen_config(self):
|
||||||
|
"""
|
||||||
|
generates a slapd.conf and returns it as one string
|
||||||
|
for generating specific static configuration files you have to
|
||||||
|
override this method
|
||||||
|
"""
|
||||||
|
include_directives = "\n".join(
|
||||||
|
'include "{schema_prefix}/{schema_file}"'.format(
|
||||||
|
schema_prefix=self._schema_prefix, schema_file=schema_file,
|
||||||
|
)
|
||||||
|
for schema_file in self.openldap_schema_files
|
||||||
|
)
|
||||||
|
config_dict = {
|
||||||
|
"serverid": hex(self.server_id),
|
||||||
|
"schema_prefix": self._schema_prefix,
|
||||||
|
"include_directives": include_directives,
|
||||||
|
"loglevel": self.slapd_loglevel,
|
||||||
|
"database": self.database,
|
||||||
|
"directory": self._db_directory,
|
||||||
|
"suffix": self.suffix,
|
||||||
|
"rootdn": self.root_dn,
|
||||||
|
"rootpw": self.root_pw,
|
||||||
|
"root_uid": os.getuid(),
|
||||||
|
"root_gid": os.getgid(),
|
||||||
|
"cafile": self.cafile,
|
||||||
|
"servercert": self.servercert,
|
||||||
|
"serverkey": self.serverkey,
|
||||||
|
}
|
||||||
|
return self.slapd_conf_template % config_dict
|
||||||
|
|
||||||
|
def _create_sub_dirs(self, dir_names):
|
||||||
|
"""
|
||||||
|
create sub-directories beneath self.testrundir
|
||||||
|
"""
|
||||||
|
for dname in dir_names:
|
||||||
|
dir_name = os.path.join(self.testrundir, dname)
|
||||||
|
self._log.debug("Create directory %s", dir_name)
|
||||||
|
os.mkdir(dir_name)
|
||||||
|
|
||||||
|
def _ln_schema_files(self, file_names, source_dir):
|
||||||
|
"""
|
||||||
|
write symbolic links to original schema files
|
||||||
|
"""
|
||||||
|
for fname in file_names:
|
||||||
|
ln_source = os.path.join(source_dir, fname)
|
||||||
|
ln_target = os.path.join(self._schema_prefix, fname)
|
||||||
|
self._log.debug("Create symlink %s -> %s", ln_source, ln_target)
|
||||||
|
os.symlink(ln_source, ln_target)
|
||||||
|
|
||||||
|
def _write_config(self):
|
||||||
|
"""Writes the slapd.conf file out, and returns the path to it."""
|
||||||
|
self._log.debug("Writing config to %s", self._slapd_conf)
|
||||||
|
with open(self._slapd_conf, "w") as config_file:
|
||||||
|
config_file.write(self.gen_config())
|
||||||
|
self._log.info("Wrote config to %s", self._slapd_conf)
|
||||||
|
|
||||||
|
def _test_config(self):
|
||||||
|
self._log.debug("testing config %s", self._slapd_conf)
|
||||||
|
popen_list = [
|
||||||
|
self.PATH_SLAPD,
|
||||||
|
"-Ttest",
|
||||||
|
"-f",
|
||||||
|
self._slapd_conf,
|
||||||
|
"-u",
|
||||||
|
]
|
||||||
|
if self._log.isEnabledFor(logging.DEBUG):
|
||||||
|
popen_list.append("-v")
|
||||||
|
popen_list.extend(["-d", "config"])
|
||||||
|
else:
|
||||||
|
popen_list.append("-Q")
|
||||||
|
proc = subprocess.Popen(popen_list)
|
||||||
|
if proc.wait() != 0:
|
||||||
|
raise RuntimeError("configuration test failed")
|
||||||
|
self._log.info("config ok: %s", self._slapd_conf)
|
||||||
|
|
||||||
|
def _start_slapd(self):
|
||||||
|
"""
|
||||||
|
Spawns/forks the slapd process
|
||||||
|
"""
|
||||||
|
urls = [self.ldap_uri]
|
||||||
|
if self.ldapi_uri:
|
||||||
|
urls.append(self.ldapi_uri)
|
||||||
|
slapd_args = [
|
||||||
|
self.PATH_SLAPD,
|
||||||
|
"-f",
|
||||||
|
self._slapd_conf,
|
||||||
|
"-F",
|
||||||
|
self.testrundir,
|
||||||
|
"-h",
|
||||||
|
" ".join(urls),
|
||||||
|
]
|
||||||
|
if self._log.isEnabledFor(logging.DEBUG):
|
||||||
|
slapd_args.extend(["-d", "-1"])
|
||||||
|
else:
|
||||||
|
slapd_args.extend(["-d", "0"])
|
||||||
|
self._log.info("starting slapd: %r", " ".join(slapd_args))
|
||||||
|
self._proc = subprocess.Popen(slapd_args)
|
||||||
|
# Waits until the LDAP server socket is open, or slapd crashed
|
||||||
|
# no cover to avoid spurious coverage changes, see
|
||||||
|
# https://github.com/python-ldap/python-ldap/issues/127
|
||||||
|
for _ in range(10): # pragma: no cover
|
||||||
|
if self._proc.poll() is not None:
|
||||||
|
self._stopped()
|
||||||
|
raise RuntimeError("slapd exited before opening port")
|
||||||
|
time.sleep(self._start_sleep)
|
||||||
|
try:
|
||||||
|
self._log.debug("slapd connection check to %s", self.default_ldap_uri)
|
||||||
|
self.ldapwhoami()
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
raise RuntimeError("slapd did not start properly")
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""
|
||||||
|
Starts the slapd server process running, and waits for it to come up.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._proc is None:
|
||||||
|
# prepare directory structure
|
||||||
|
atexit.register(self.stop)
|
||||||
|
self._cleanup_rundir()
|
||||||
|
self.setup_rundir()
|
||||||
|
self._write_config()
|
||||||
|
self._test_config()
|
||||||
|
self._start_slapd()
|
||||||
|
self._log.debug(
|
||||||
|
"slapd with pid=%d listening on %s and %s",
|
||||||
|
self._proc.pid,
|
||||||
|
self.ldap_uri,
|
||||||
|
self.ldapi_uri,
|
||||||
|
)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""
|
||||||
|
Stops the slapd server, and waits for it to terminate and cleans up
|
||||||
|
"""
|
||||||
|
if self._proc is not None:
|
||||||
|
self._log.debug("stopping slapd with pid %d", self._proc.pid)
|
||||||
|
self._proc.terminate()
|
||||||
|
self.wait()
|
||||||
|
self._cleanup_rundir()
|
||||||
|
if hasattr(atexit, "unregister"):
|
||||||
|
# Python 3
|
||||||
|
atexit.unregister(self.stop)
|
||||||
|
elif hasattr(atexit, "_exithandlers"):
|
||||||
|
# Python 2, can be None during process shutdown
|
||||||
|
try:
|
||||||
|
atexit._exithandlers.remove(self.stop)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def restart(self):
|
||||||
|
"""
|
||||||
|
Restarts the slapd server with same data
|
||||||
|
"""
|
||||||
|
self._proc.terminate()
|
||||||
|
self.wait()
|
||||||
|
self._start_slapd()
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
"""Waits for the slapd process to terminate by itself."""
|
||||||
|
if self._proc:
|
||||||
|
self._proc.wait()
|
||||||
|
self._stopped()
|
||||||
|
|
||||||
|
def _stopped(self):
|
||||||
|
"""Called when the slapd server is known to have terminated"""
|
||||||
|
if self._proc is not None:
|
||||||
|
self._log.info("slapd[%d] terminated", self._proc.pid)
|
||||||
|
self._proc = None
|
||||||
|
|
||||||
|
def _cli_auth_args(self):
|
||||||
|
if self.cli_sasl_external:
|
||||||
|
authc_args = [
|
||||||
|
"-Y",
|
||||||
|
"EXTERNAL",
|
||||||
|
]
|
||||||
|
if not self._log.isEnabledFor(logging.DEBUG):
|
||||||
|
authc_args.append("-Q")
|
||||||
|
else:
|
||||||
|
authc_args = [
|
||||||
|
"-x",
|
||||||
|
"-D",
|
||||||
|
self.root_dn,
|
||||||
|
"-w",
|
||||||
|
self.root_pw,
|
||||||
|
]
|
||||||
|
return authc_args
|
||||||
|
|
||||||
|
# no cover to avoid spurious coverage changes
|
||||||
|
def _cli_popen(
|
||||||
|
self, ldapcommand, extra_args=None, ldap_uri=None, stdin_data=None
|
||||||
|
): # pragma: no cover
|
||||||
|
if ldap_uri is None:
|
||||||
|
ldap_uri = self.default_ldap_uri
|
||||||
|
args = (
|
||||||
|
[ldapcommand, "-H", ldap_uri,] + self._cli_auth_args() + (extra_args or [])
|
||||||
|
)
|
||||||
|
self._log.debug("Run command: %r", " ".join(args))
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
self._log.debug("stdin_data=%r", stdin_data)
|
||||||
|
stdout_data, stderr_data = proc.communicate(stdin_data)
|
||||||
|
if stdout_data is not None:
|
||||||
|
self._log.debug("stdout_data=%r", stdout_data)
|
||||||
|
if stderr_data is not None:
|
||||||
|
self._log.debug("stderr_data=%r", stderr_data)
|
||||||
|
if proc.wait() != 0:
|
||||||
|
raise RuntimeError(
|
||||||
|
"{!r} process failed:\n{!r}\n{!r}".format(
|
||||||
|
args, stdout_data, stderr_data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return stdout_data, stderr_data
|
||||||
|
|
||||||
|
def ldapwhoami(self, extra_args=None):
|
||||||
|
"""
|
||||||
|
Runs ldapwhoami on this slapd instance
|
||||||
|
"""
|
||||||
|
self._cli_popen(self.PATH_LDAPWHOAMI, extra_args=extra_args)
|
||||||
|
|
||||||
|
def ldapadd(self, ldif, extra_args=None):
|
||||||
|
"""
|
||||||
|
Runs ldapadd on this slapd instance, passing it the ldif content
|
||||||
|
"""
|
||||||
|
self._cli_popen(
|
||||||
|
self.PATH_LDAPADD, extra_args=extra_args, stdin_data=ldif.encode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
def ldapmodify(self, ldif, extra_args=None):
|
||||||
|
"""
|
||||||
|
Runs ldapadd on this slapd instance, passing it the ldif content
|
||||||
|
"""
|
||||||
|
self._cli_popen(
|
||||||
|
self.PATH_LDAPMODIFY, extra_args=extra_args, stdin_data=ldif.encode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
def ldapdelete(self, dn, recursive=False, extra_args=None):
|
||||||
|
"""
|
||||||
|
Runs ldapdelete on this slapd instance, deleting 'dn'
|
||||||
|
"""
|
||||||
|
if extra_args is None:
|
||||||
|
extra_args = []
|
||||||
|
if recursive:
|
||||||
|
extra_args.append("-r")
|
||||||
|
extra_args.append(dn)
|
||||||
|
self._cli_popen(self.PATH_LDAPDELETE, extra_args=extra_args)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
|
||||||
|
class SlapdTestCase(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
test class which also clones or initializes a running slapd
|
||||||
|
"""
|
||||||
|
|
||||||
|
server_class = SlapdObject
|
||||||
|
server = None
|
||||||
|
ldap_object_class = None
|
||||||
|
|
||||||
|
def _open_ldap_conn(self, who=None, cred=None, **kwargs):
|
||||||
|
"""
|
||||||
|
return a LDAPObject instance after simple bind
|
||||||
|
"""
|
||||||
|
ldap_conn = self.ldap_object_class(self.server.ldap_uri, **kwargs)
|
||||||
|
ldap_conn.protocol_version = 3
|
||||||
|
# ldap_conn.set_option(ldap.OPT_REFERRALS, 0)
|
||||||
|
ldap_conn.simple_bind_s(who or self.server.root_dn, cred or self.server.root_pw)
|
||||||
|
return ldap_conn
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.server = cls.server_class()
|
||||||
|
cls.server.start()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
cls.server.stop()
|
24
test/src/old_slapdtest/certs/README
Normal file
24
test/src/old_slapdtest/certs/README
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
python-ldap test certificates
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Certificates and keys
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
* ``ca.pem``: internal root CA certificate
|
||||||
|
* ``server.pem``: TLS server certificate for slapd, signed by root CA. The
|
||||||
|
server cert is valid for DNS Name ``localhost`` and IPs ``127.0.0.1`` and
|
||||||
|
``:1``.
|
||||||
|
* ``server.key``: private key for ``server.pem``, no password protection
|
||||||
|
* ``client.pem``: certificate for TLS client cert authentication, signed by
|
||||||
|
root CA.
|
||||||
|
* ``client.key``: private key for ``client.pem``, no password protection
|
||||||
|
|
||||||
|
Configuration and scripts
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
* ``ca.conf`` contains the CA definition as well as extensions for the
|
||||||
|
client and server certificates.
|
||||||
|
* ``client.conf`` and ``server.conf`` hold the subject and base configuration
|
||||||
|
for server and client certs.
|
||||||
|
* ``gencerts.sh`` creates new CA, client and server certificates.
|
||||||
|
* ``gennssdb.sh`` can be used to create a NSSDB for all certs and keys.
|
77
test/src/old_slapdtest/certs/ca.conf
Normal file
77
test/src/old_slapdtest/certs/ca.conf
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
# Written by Christian Heimes
|
||||||
|
|
||||||
|
[default]
|
||||||
|
ca = "ca"
|
||||||
|
tmpdir = $ENV::CATMPDIR
|
||||||
|
outdir = $ENV::CAOUTDIR
|
||||||
|
name_opt = multiline,-esc_msb,utf8
|
||||||
|
|
||||||
|
[req]
|
||||||
|
default_bits = 2048
|
||||||
|
encrypt_key = no
|
||||||
|
default_md = sha256
|
||||||
|
utf8 = yes
|
||||||
|
string_mask = utf8only
|
||||||
|
prompt = no
|
||||||
|
distinguished_name = ca_dn
|
||||||
|
|
||||||
|
[ca_dn]
|
||||||
|
countryName = "DE"
|
||||||
|
organizationName = "python-ldap"
|
||||||
|
organizationalUnitName = "slapd-test"
|
||||||
|
commonName = "Python LDAP Test CA"
|
||||||
|
|
||||||
|
[ca]
|
||||||
|
default_ca = python_ldap_ca
|
||||||
|
|
||||||
|
[python_ldap_ca]
|
||||||
|
certificate = $outdir/$ca.pem
|
||||||
|
private_key = $outdir/$ca.key
|
||||||
|
new_certs_dir = $tmpdir
|
||||||
|
serial = $tmpdir/$ca.crt.srl
|
||||||
|
crlnumber = $tmpdir/$ca.crl.srl
|
||||||
|
database = $tmpdir/$ca.db
|
||||||
|
unique_subject = no
|
||||||
|
default_days = 365200
|
||||||
|
default_md = sha256
|
||||||
|
policy = match_pol
|
||||||
|
email_in_dn = no
|
||||||
|
preserve = no
|
||||||
|
name_opt = $name_opt
|
||||||
|
cert_opt = ca_default
|
||||||
|
copy_extensions = none
|
||||||
|
default_crl_days = 365100
|
||||||
|
|
||||||
|
[match_pol]
|
||||||
|
countryName = match
|
||||||
|
stateOrProvinceName = optional
|
||||||
|
localityName = optional
|
||||||
|
organizationName = match
|
||||||
|
organizationalUnitName = match
|
||||||
|
commonName = supplied
|
||||||
|
|
||||||
|
[ca_ext]
|
||||||
|
basicConstraints = critical,CA:true
|
||||||
|
keyUsage = critical,keyCertSign,cRLSign
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
authorityKeyIdentifier = keyid:always
|
||||||
|
|
||||||
|
[server_san]
|
||||||
|
DNS.1 = localhost
|
||||||
|
IP.1 = 127.0.0.1
|
||||||
|
IP.2 = ::1
|
||||||
|
|
||||||
|
[server_ext]
|
||||||
|
basicConstraints = critical,CA:false
|
||||||
|
keyUsage = critical,digitalSignature,keyEncipherment
|
||||||
|
extendedKeyUsage = critical,serverAuth
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
authorityKeyIdentifier = keyid:always
|
||||||
|
subjectAltName = @server_san
|
||||||
|
|
||||||
|
[client_ext]
|
||||||
|
basicConstraints = critical,CA:false
|
||||||
|
keyUsage = critical,digitalSignature
|
||||||
|
extendedKeyUsage = critical,clientAuth
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
authorityKeyIdentifier = keyid:always
|
80
test/src/old_slapdtest/certs/ca.pem
Normal file
80
test/src/old_slapdtest/certs/ca.pem
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 1 (0x1)
|
||||||
|
Signature Algorithm: sha256WithRSAEncryption
|
||||||
|
Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA
|
||||||
|
Validity
|
||||||
|
Not Before: Apr 12 18:52:38 2019 GMT
|
||||||
|
Not After : Oct 17 18:52:38 2994 GMT
|
||||||
|
Subject: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (2048 bit)
|
||||||
|
Modulus:
|
||||||
|
00:d7:30:73:20:44:7d:83:d4:c7:01:b8:ab:1e:7c:
|
||||||
|
91:f4:38:ac:9c:41:43:64:0c:31:99:48:70:22:7d:
|
||||||
|
ae:1b:47:e7:2a:28:4d:f7:46:4e:b4:ba:ae:c0:9d:
|
||||||
|
d5:1f:4b:7a:79:2f:b9:dc:68:7f:79:84:88:50:51:
|
||||||
|
3b:7d:dc:d5:57:17:66:45:c0:2c:20:13:f7:99:d6:
|
||||||
|
9d:e2:12:7c:41:76:82:51:19:2c:b6:ff:46:cb:04:
|
||||||
|
56:38:22:2a:c3:7a:b5:71:51:49:4e:62:68:a0:99:
|
||||||
|
6f:de:f3:a2:0f:a2:aa:1b:72:a5:87:bc:42:5a:a7:
|
||||||
|
22:8d:33:b4:88:a8:dc:5d:72:ca:dd:a0:9a:4e:db:
|
||||||
|
7d:8b:10:de:c5:41:e9:e9:8d:fa:6c:dd:94:6e:b1:
|
||||||
|
31:c2:6d:a1:69:6c:7a:3a:b2:76:65:c9:e5:95:38:
|
||||||
|
62:40:81:c6:29:26:26:d1:d1:c1:f4:5e:fa:24:ef:
|
||||||
|
13:da:24:13:6f:f5:5c:ba:b1:31:8f:30:94:71:7b:
|
||||||
|
c6:e5:da:b9:b5:64:39:39:09:c2:4a:80:64:58:1d:
|
||||||
|
99:f5:65:3c:a7:26:08:95:26:35:7b:fa:e7:20:08:
|
||||||
|
ff:72:df:9b:8f:9f:da:8b:c3:a7:8b:fc:8c:c0:a5:
|
||||||
|
31:87:1d:4c:14:f6:cf:90:5e:2e:6e:a6:db:27:08:
|
||||||
|
eb:df
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Basic Constraints: critical
|
||||||
|
CA:TRUE
|
||||||
|
X509v3 Key Usage: critical
|
||||||
|
Certificate Sign, CRL Sign
|
||||||
|
X509v3 Subject Key Identifier:
|
||||||
|
BD:78:D5:4A:F1:90:96:C5:E8:EC:66:49:23:47:03:5F:26:73:86:B2
|
||||||
|
X509v3 Authority Key Identifier:
|
||||||
|
keyid:BD:78:D5:4A:F1:90:96:C5:E8:EC:66:49:23:47:03:5F:26:73:86:B2
|
||||||
|
|
||||||
|
Signature Algorithm: sha256WithRSAEncryption
|
||||||
|
06:20:1f:eb:42:6a:42:62:b1:ee:69:c8:cd:47:a6:2e:69:95:
|
||||||
|
59:dc:49:09:69:40:93:25:a1:ec:6d:3a:dd:dc:e5:74:ab:33:
|
||||||
|
9d:8f:cc:e3:bb:7a:3f:5b:51:58:74:f7:bd:6c:7c:3c:b6:5a:
|
||||||
|
05:50:a8:8c:c3:fb:5b:75:2a:c2:6c:06:93:4c:a9:93:71:1c:
|
||||||
|
51:e5:be:a1:24:93:e2:79:ca:ea:08:86:90:b9:70:e7:7a:40:
|
||||||
|
bf:f4:d6:71:f4:4d:c0:0f:e0:31:a0:23:46:77:30:72:a9:62:
|
||||||
|
8a:2a:12:c4:dd:3d:86:ae:f7:6b:33:80:26:58:49:53:ff:cd:
|
||||||
|
8a:c6:f6:11:2c:b3:ff:a5:8e:1c:f8:22:e2:1b:8e:04:33:fb:
|
||||||
|
0d:da:31:86:12:9f:d1:03:86:9c:6a:78:5e:3c:5e:8a:52:aa:
|
||||||
|
68:1f:ff:f9:17:75:b0:da:f2:99:3c:80:3c:96:2a:33:07:54:
|
||||||
|
59:84:e7:92:34:0f:99:76:e3:d6:4d:4d:9c:fb:21:35:f9:cb:
|
||||||
|
a5:30:80:8b:9d:61:90:d3:d4:59:3a:2f:f2:f6:20:13:7e:26:
|
||||||
|
dc:50:b0:49:3e:19:fe:eb:7d:cf:b9:1a:5d:5c:3a:76:30:d9:
|
||||||
|
0e:d7:df:de:ce:a9:c4:21:df:63:b9:d0:64:86:0b:28:9a:2e:
|
||||||
|
ab:51:73:e4
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDjDCCAnSgAwIBAgIBATANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU
|
||||||
|
MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV
|
||||||
|
BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwIBcNMTkwNDEyMTg1MjM4WhgPMjk5NDEw
|
||||||
|
MTcxODUyMzhaMFYxCzAJBgNVBAYTAkRFMRQwEgYDVQQKDAtweXRob24tbGRhcDET
|
||||||
|
MBEGA1UECwwKc2xhcGQtdGVzdDEcMBoGA1UEAwwTUHl0aG9uIExEQVAgVGVzdCBD
|
||||||
|
QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANcwcyBEfYPUxwG4qx58
|
||||||
|
kfQ4rJxBQ2QMMZlIcCJ9rhtH5yooTfdGTrS6rsCd1R9Lenkvudxof3mEiFBRO33c
|
||||||
|
1VcXZkXALCAT95nWneISfEF2glEZLLb/RssEVjgiKsN6tXFRSU5iaKCZb97zog+i
|
||||||
|
qhtypYe8QlqnIo0ztIio3F1yyt2gmk7bfYsQ3sVB6emN+mzdlG6xMcJtoWlsejqy
|
||||||
|
dmXJ5ZU4YkCBxikmJtHRwfRe+iTvE9okE2/1XLqxMY8wlHF7xuXaubVkOTkJwkqA
|
||||||
|
ZFgdmfVlPKcmCJUmNXv65yAI/3Lfm4+f2ovDp4v8jMClMYcdTBT2z5BeLm6m2ycI
|
||||||
|
698CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
|
||||||
|
VR0OBBYEFL141UrxkJbF6OxmSSNHA18mc4ayMB8GA1UdIwQYMBaAFL141UrxkJbF
|
||||||
|
6OxmSSNHA18mc4ayMA0GCSqGSIb3DQEBCwUAA4IBAQAGIB/rQmpCYrHuacjNR6Yu
|
||||||
|
aZVZ3EkJaUCTJaHsbTrd3OV0qzOdj8zju3o/W1FYdPe9bHw8tloFUKiMw/tbdSrC
|
||||||
|
bAaTTKmTcRxR5b6hJJPiecrqCIaQuXDnekC/9NZx9E3AD+AxoCNGdzByqWKKKhLE
|
||||||
|
3T2GrvdrM4AmWElT/82KxvYRLLP/pY4c+CLiG44EM/sN2jGGEp/RA4acanhePF6K
|
||||||
|
UqpoH//5F3Ww2vKZPIA8liozB1RZhOeSNA+ZduPWTU2c+yE1+culMICLnWGQ09RZ
|
||||||
|
Oi/y9iATfibcULBJPhn+633PuRpdXDp2MNkO19/ezqnEId9judBkhgsomi6rUXPk
|
||||||
|
-----END CERTIFICATE-----
|
16
test/src/old_slapdtest/certs/client.conf
Normal file
16
test/src/old_slapdtest/certs/client.conf
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Written by Christian Heimes
|
||||||
|
|
||||||
|
[req]
|
||||||
|
default_bits = 2048
|
||||||
|
encrypt_key = no
|
||||||
|
default_md = sha256
|
||||||
|
utf8 = yes
|
||||||
|
string_mask = utf8only
|
||||||
|
prompt = no
|
||||||
|
distinguished_name = client_dn
|
||||||
|
|
||||||
|
[client_dn]
|
||||||
|
countryName = "DE"
|
||||||
|
organizationName = "python-ldap"
|
||||||
|
organizationalUnitName = "slapd-test"
|
||||||
|
commonName = "client"
|
28
test/src/old_slapdtest/certs/client.key
Normal file
28
test/src/old_slapdtest/certs/client.key
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDjt5O6nRrnAWPm
|
||||||
|
T0JvRLBHMclll92IWF/O4GEdcJ5fbBxP3BxK0Dv+6aRcR7b2o0f6fk/bgNepXfv/
|
||||||
|
MXDQcFlESbfmUNGshFmZr0sjPrYPD1R06TZs+/7RsMXnx1c79mFGEQ4wqzDOBHKQ
|
||||||
|
xeDhNJk+BcE0QABsqF8AA2XC2/dK14QCljKLC84k1zTFTnh8duN2eAalaPQFFOoj
|
||||||
|
4AnonUnswJ45zIx5V2BdG+oqO5dwo/cEukKgAEL8T2IJ9Cqlmh2sPbMqYC8cODq6
|
||||||
|
YcugMznxrfHV5LNThfkvwMe26+vv68r65zalPDy0M+cUMTMyBVY4TL3fejrloY2t
|
||||||
|
YMhPJIclAgMBAAECggEAPXdd/u9NRbGQX6hhTFuEIZOEw1F80MLaCaNzU1kExskN
|
||||||
|
01icom0W5LX4UZhiAK0OTsUtlRhwHh1qWfXkd777uX0UkKycDC8laGByra7Nwb7n
|
||||||
|
ky8oK77Rh5RptyiNmXflxd3wsJ5k7BczPXTMQL3L53vyLMJh2vKPwhcorrJlS+Pi
|
||||||
|
JjINMaR4IrDlpMYlrn9NTjsGr+mj/pdmKfU/KVXeKzFcwKTjUnDJNSbGDIC0AxaJ
|
||||||
|
dGU0yIX9MPW+p5szcA9o22UWW4LsEFY4YABeCqbm9/UQt3jWVMjCy4AOgr/9HWSR
|
||||||
|
DvXI/Xtdl3CTCr8+qDnhBaUI27z+UelZfTBFKUb8AQKBgQD6SmtrTBgEfb6tuxJw
|
||||||
|
AAHRuUcWGjatZ7X+meHRC9B7UPxUrKl9tU5NC7Gz6YMt+vr4bNMwykI6Ndj+4tSJ
|
||||||
|
KqsAC86v19CH4usMBLZ68MeTRvtQGiPah71syYrxf0uvYOx/KzUUBX240Ls+lEbE
|
||||||
|
W33psMoNAezUPpJwKx7CMjcBgQKBgQDo6VaT59bKRc3DXJvqFjd7TPIex+ny6JK+
|
||||||
|
8oOwyyFFBwkzfymoOxN4lxSrE6yf7uTemRRn+RIH3UGDottIDqzhjvtcV5uODeIN
|
||||||
|
8WzxTbl759qIxt+z7aF7SkwJLJAAZS3qqCXKtMBo7ln4xKaoRLT2RohqD1YXGrg8
|
||||||
|
wmYcUZoPpQKBgQCm2QVSuZ8pH0oFNjfMQbT0wbYJnd/lKMXBu4M1f9Ky4gHT0GYM
|
||||||
|
Ttirs6f6byfrduvmv2TpmWscsti80SktZywnE7fssMlqTHKzyFB9FBV2sFLHyyUr
|
||||||
|
gGFeK9xbsKgbeVkuTPdNKXvtv/eSd/XU38jIB/opQadGtY+ZBqWyfxb8AQKBgBLc
|
||||||
|
SlmBzZ/llSr7xdhn4ihG69hYQfacpL13r/hSCqinUDRuWLY5ynLacR8FYdY1pyzr
|
||||||
|
Yn6k6bPfU93QA0fLgG5ngK1SntMbBrIwWa0UqS+Cb+zhhd3xIUF1m8CmbibKCrTU
|
||||||
|
1vKaPnaAzqJZclFv9uN2hLdp9IO8cyzgZRpn9TzNAoGAUfZF1983qknfBgD8Lgm3
|
||||||
|
zzKYtc8q2Ukatfo4VCp66CEprbLcBq5mKx6JiBoMGqU8SI5XVG0F0aHH2n8gImcu
|
||||||
|
bO0vtEldDc1ylZ/H7xhHFWlMzmTlsbHdHVtetFfKLTpjq6duvgLA12lJNHNVu3OU
|
||||||
|
Z1bRWDeZIP70+jdYrmSoVi8=
|
||||||
|
-----END PRIVATE KEY-----
|
83
test/src/old_slapdtest/certs/client.pem
Normal file
83
test/src/old_slapdtest/certs/client.pem
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 3 (0x3)
|
||||||
|
Signature Algorithm: sha256WithRSAEncryption
|
||||||
|
Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA
|
||||||
|
Validity
|
||||||
|
Not Before: Apr 12 18:52:38 2019 GMT
|
||||||
|
Not After : Mar 1 18:52:38 3019 GMT
|
||||||
|
Subject: C=DE, O=python-ldap, OU=slapd-test, CN=client
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (2048 bit)
|
||||||
|
Modulus:
|
||||||
|
00:e3:b7:93:ba:9d:1a:e7:01:63:e6:4f:42:6f:44:
|
||||||
|
b0:47:31:c9:65:97:dd:88:58:5f:ce:e0:61:1d:70:
|
||||||
|
9e:5f:6c:1c:4f:dc:1c:4a:d0:3b:fe:e9:a4:5c:47:
|
||||||
|
b6:f6:a3:47:fa:7e:4f:db:80:d7:a9:5d:fb:ff:31:
|
||||||
|
70:d0:70:59:44:49:b7:e6:50:d1:ac:84:59:99:af:
|
||||||
|
4b:23:3e:b6:0f:0f:54:74:e9:36:6c:fb:fe:d1:b0:
|
||||||
|
c5:e7:c7:57:3b:f6:61:46:11:0e:30:ab:30:ce:04:
|
||||||
|
72:90:c5:e0:e1:34:99:3e:05:c1:34:40:00:6c:a8:
|
||||||
|
5f:00:03:65:c2:db:f7:4a:d7:84:02:96:32:8b:0b:
|
||||||
|
ce:24:d7:34:c5:4e:78:7c:76:e3:76:78:06:a5:68:
|
||||||
|
f4:05:14:ea:23:e0:09:e8:9d:49:ec:c0:9e:39:cc:
|
||||||
|
8c:79:57:60:5d:1b:ea:2a:3b:97:70:a3:f7:04:ba:
|
||||||
|
42:a0:00:42:fc:4f:62:09:f4:2a:a5:9a:1d:ac:3d:
|
||||||
|
b3:2a:60:2f:1c:38:3a:ba:61:cb:a0:33:39:f1:ad:
|
||||||
|
f1:d5:e4:b3:53:85:f9:2f:c0:c7:b6:eb:eb:ef:eb:
|
||||||
|
ca:fa:e7:36:a5:3c:3c:b4:33:e7:14:31:33:32:05:
|
||||||
|
56:38:4c:bd:df:7a:3a:e5:a1:8d:ad:60:c8:4f:24:
|
||||||
|
87:25
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Basic Constraints: critical
|
||||||
|
CA:FALSE
|
||||||
|
X509v3 Key Usage: critical
|
||||||
|
Digital Signature
|
||||||
|
X509v3 Extended Key Usage: critical
|
||||||
|
TLS Web Client Authentication
|
||||||
|
X509v3 Subject Key Identifier:
|
||||||
|
4F:E7:35:C7:C8:C1:01:C3:7C:53:86:B9:BF:AE:8B:D6:45:A2:78:20
|
||||||
|
X509v3 Authority Key Identifier:
|
||||||
|
keyid:BD:78:D5:4A:F1:90:96:C5:E8:EC:66:49:23:47:03:5F:26:73:86:B2
|
||||||
|
|
||||||
|
Signature Algorithm: sha256WithRSAEncryption
|
||||||
|
1c:90:5f:cf:18:48:95:4d:9d:d3:8e:6d:d1:69:19:1e:7b:3f:
|
||||||
|
1f:48:7c:c8:0d:2f:c4:53:0f:89:23:f4:be:ea:b4:7a:c6:dd:
|
||||||
|
cc:18:0f:e7:34:ea:2c:d4:07:0d:65:78:e8:20:40:3f:36:ef:
|
||||||
|
2c:00:31:69:e6:20:48:65:be:57:03:0e:69:ff:b9:83:59:99:
|
||||||
|
7d:4d:86:98:14:5b:8e:39:25:3a:a8:6d:51:dc:45:a5:0f:cd:
|
||||||
|
f3:7a:fd:55:af:5f:55:75:20:03:f5:4a:75:6a:79:2f:76:84:
|
||||||
|
f6:4e:3d:1d:59:45:9a:b1:6a:57:6f:16:76:76:f8:df:6e:96:
|
||||||
|
d5:25:27:34:4b:21:d8:c9:9a:36:55:45:a0:43:16:43:68:93:
|
||||||
|
37:af:81:89:06:d1:56:1b:9e:0f:62:40:ad:3c:4c:f5:ef:6c:
|
||||||
|
a2:a4:7f:f2:fa:78:9c:0d:c0:19:f1:10:e8:d8:cf:03:67:3c:
|
||||||
|
2d:4d:f3:5d:67:5c:41:a7:4f:d6:c5:0e:ff:2c:04:dd:23:bb:
|
||||||
|
85:44:8e:25:ac:15:a3:82:fa:a4:4f:fa:1d:87:f0:58:dc:ae:
|
||||||
|
53:05:b9:81:e8:cb:e5:0c:ac:a5:74:68:03:f9:22:a0:45:b6:
|
||||||
|
62:58:e0:98:d9:8c:54:a4:22:03:7a:37:12:eb:7d:b1:ad:45:
|
||||||
|
60:8e:7a:df
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDlDCCAnygAwIBAgIBAzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU
|
||||||
|
MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV
|
||||||
|
BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwIBcNMTkwNDEyMTg1MjM4WhgPMzAxOTAz
|
||||||
|
MDExODUyMzhaMEkxCzAJBgNVBAYTAkRFMRQwEgYDVQQKDAtweXRob24tbGRhcDET
|
||||||
|
MBEGA1UECwwKc2xhcGQtdGVzdDEPMA0GA1UEAwwGY2xpZW50MIIBIjANBgkqhkiG
|
||||||
|
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA47eTup0a5wFj5k9Cb0SwRzHJZZfdiFhfzuBh
|
||||||
|
HXCeX2wcT9wcStA7/umkXEe29qNH+n5P24DXqV37/zFw0HBZREm35lDRrIRZma9L
|
||||||
|
Iz62Dw9UdOk2bPv+0bDF58dXO/ZhRhEOMKswzgRykMXg4TSZPgXBNEAAbKhfAANl
|
||||||
|
wtv3SteEApYyiwvOJNc0xU54fHbjdngGpWj0BRTqI+AJ6J1J7MCeOcyMeVdgXRvq
|
||||||
|
KjuXcKP3BLpCoABC/E9iCfQqpZodrD2zKmAvHDg6umHLoDM58a3x1eSzU4X5L8DH
|
||||||
|
tuvr7+vK+uc2pTw8tDPnFDEzMgVWOEy933o65aGNrWDITySHJQIDAQABo3gwdjAM
|
||||||
|
BgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEF
|
||||||
|
BQcDAjAdBgNVHQ4EFgQUT+c1x8jBAcN8U4a5v66L1kWieCAwHwYDVR0jBBgwFoAU
|
||||||
|
vXjVSvGQlsXo7GZJI0cDXyZzhrIwDQYJKoZIhvcNAQELBQADggEBAByQX88YSJVN
|
||||||
|
ndOObdFpGR57Px9IfMgNL8RTD4kj9L7qtHrG3cwYD+c06izUBw1leOggQD827ywA
|
||||||
|
MWnmIEhlvlcDDmn/uYNZmX1NhpgUW445JTqobVHcRaUPzfN6/VWvX1V1IAP1SnVq
|
||||||
|
eS92hPZOPR1ZRZqxaldvFnZ2+N9ultUlJzRLIdjJmjZVRaBDFkNokzevgYkG0VYb
|
||||||
|
ng9iQK08TPXvbKKkf/L6eJwNwBnxEOjYzwNnPC1N811nXEGnT9bFDv8sBN0ju4VE
|
||||||
|
jiWsFaOC+qRP+h2H8FjcrlMFuYHoy+UMrKV0aAP5IqBFtmJY4JjZjFSkIgN6NxLr
|
||||||
|
fbGtRWCOet8=
|
||||||
|
-----END CERTIFICATE-----
|
68
test/src/old_slapdtest/certs/gencerts.sh
Executable file
68
test/src/old_slapdtest/certs/gencerts.sh
Executable file
|
@ -0,0 +1,68 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Written by Christian Heimes
|
||||||
|
set -e
|
||||||
|
|
||||||
|
export CAOUTDIR=.
|
||||||
|
export CATMPDIR=tmp
|
||||||
|
|
||||||
|
rm -rf $CATMPDIR
|
||||||
|
rm -rf ca.pem ca.key server.pem server.key client.pem client.key
|
||||||
|
rm -rf cert9.db key4.db pkcs11.tx
|
||||||
|
|
||||||
|
mkdir -p $CAOUTDIR
|
||||||
|
mkdir -p $CATMPDIR
|
||||||
|
|
||||||
|
touch $CATMPDIR/ca.db
|
||||||
|
touch $CATMPDIR/ca.db.attr
|
||||||
|
echo '01' > $CATMPDIR/ca.crt.srl
|
||||||
|
echo '01' > $CATMPDIR/ca.crl.srl
|
||||||
|
|
||||||
|
# root CA
|
||||||
|
openssl req -new \
|
||||||
|
-config ca.conf \
|
||||||
|
-out $CATMPDIR/ca.csr \
|
||||||
|
-keyout $CAOUTDIR/ca.key \
|
||||||
|
-batch
|
||||||
|
|
||||||
|
openssl ca -selfsign \
|
||||||
|
-config ca.conf \
|
||||||
|
-in $CATMPDIR/ca.csr \
|
||||||
|
-out $CAOUTDIR/ca.pem \
|
||||||
|
-extensions ca_ext \
|
||||||
|
-days 356300 \
|
||||||
|
-batch
|
||||||
|
|
||||||
|
# server cert
|
||||||
|
openssl req -new \
|
||||||
|
-config server.conf \
|
||||||
|
-out $CATMPDIR/server.csr \
|
||||||
|
-keyout $CAOUTDIR/server.key \
|
||||||
|
-batch
|
||||||
|
|
||||||
|
openssl ca \
|
||||||
|
-config ca.conf \
|
||||||
|
-in $CATMPDIR/server.csr \
|
||||||
|
-out $CAOUTDIR/server.pem \
|
||||||
|
-policy match_pol \
|
||||||
|
-extensions server_ext \
|
||||||
|
-batch
|
||||||
|
|
||||||
|
# client cert
|
||||||
|
openssl req -new \
|
||||||
|
-config client.conf \
|
||||||
|
-out $CATMPDIR/client.csr \
|
||||||
|
-keyout $CAOUTDIR/client.key \
|
||||||
|
-batch
|
||||||
|
|
||||||
|
openssl ca \
|
||||||
|
-config ca.conf \
|
||||||
|
-in $CATMPDIR/client.csr \
|
||||||
|
-out $CAOUTDIR/client.pem \
|
||||||
|
-policy match_pol \
|
||||||
|
-extensions client_ext \
|
||||||
|
-batch
|
||||||
|
|
||||||
|
# cleanup
|
||||||
|
rm -rf $CATMPDIR ca.key
|
||||||
|
|
||||||
|
echo DONE
|
28
test/src/old_slapdtest/certs/gennssdb.sh
Executable file
28
test/src/old_slapdtest/certs/gennssdb.sh
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Written by Christian Heimes
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CATMPDIR=tmp
|
||||||
|
PASSFILE=${CATMPDIR}/passwd.txt
|
||||||
|
NSSDB=sql:${CAOUTDIR}
|
||||||
|
|
||||||
|
mkdir -p $CATMPDIR
|
||||||
|
|
||||||
|
# Create PKCS#12 files for NSSDB import
|
||||||
|
echo "dummy" > $PASSFILE
|
||||||
|
openssl pkcs12 -name "servercert" -in server.pem -inkey server.key \
|
||||||
|
-caname "testca" -CAfile ca.pem \
|
||||||
|
-password "file:${PASSFILE}" -export -out server.p12
|
||||||
|
openssl pkcs12 -name "clientcert" -in client.pem -inkey client.key \
|
||||||
|
-caname "testca" -CAfile ca.pem \
|
||||||
|
-password "file:${PASSFILE}" -export -out client.p12
|
||||||
|
|
||||||
|
# Create NSS DB
|
||||||
|
certutil -d $NSSDB -N --empty-password
|
||||||
|
certutil -d $NSSDB -A -n "testca" -t CT,, -a -i ca.pem
|
||||||
|
pk12util -d $NSSDB -i server.p12 -w ${PASSFILE}
|
||||||
|
pk12util -d $NSSDB -i client.p12 -w ${PASSFILE}
|
||||||
|
certutil -d $NSSDB -L
|
||||||
|
|
||||||
|
# cleanup
|
||||||
|
rm -rf $CATMPDIR server.p12 client.p12
|
16
test/src/old_slapdtest/certs/server.conf
Normal file
16
test/src/old_slapdtest/certs/server.conf
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Written by Christian Heimes
|
||||||
|
|
||||||
|
[req]
|
||||||
|
default_bits = 2048
|
||||||
|
encrypt_key = no
|
||||||
|
default_md = sha256
|
||||||
|
utf8 = yes
|
||||||
|
string_mask = utf8only
|
||||||
|
prompt = no
|
||||||
|
distinguished_name = server_dn
|
||||||
|
|
||||||
|
[server_dn]
|
||||||
|
countryName = "DE"
|
||||||
|
organizationName = "python-ldap"
|
||||||
|
organizationalUnitName = "slapd-test"
|
||||||
|
commonName = "server cert for localhost"
|
28
test/src/old_slapdtest/certs/server.key
Normal file
28
test/src/old_slapdtest/certs/server.key
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCsBk0ml3ERFJyg
|
||||||
|
I6ujIJYERVU4doTZZd4r4z/LOef0hyiYiIQAc9wetaoZpM+bl4Eherxy9SBaCBwR
|
||||||
|
zefbaYQz2f2hdEDb+sISOiTke1eiF2ugYNlS55Wk1KnCnORE9bjcSNLPsscoUSzE
|
||||||
|
2bnBSoUwdiVK18YOCZR6GTeC8eA3ekvlR+9g+FBOgQ9+StXPDdq+iIAGXZREJIua
|
||||||
|
munErtTOw85De4YFCnzGw3UeCITDD4wFmI2IWphRFwWPsSDwUJfATA8S+7Rm4vwr
|
||||||
|
Qj726gUDlicTzPXKhJjXjj6XL7xXHfpQwMPkBCrxesKceHMJ+mrRsuuqHciuixRi
|
||||||
|
g94mILElAgMBAAECggEADG5oJOHMye8zYl8xiBhSvvxDrFDkSNGTvJgvhAArQwCB
|
||||||
|
boRvBZlZzt5R7Ih8eEH6kvDLrYMJU3hCjwbSOojlhNm7+m7sQPleDPMmt1wyeQQ4
|
||||||
|
Qt681cDmj4LOwcGUvWcEdObOVTQWMFOtaIxTYCSCe34OM9pj9Z+7mxc3a78O9PND
|
||||||
|
Ib/CwcTA1OyoupzkKirqkdLXwK3x2aT/1TMaPX94taHB51cxXc7AglL9QnuCkuaG
|
||||||
|
krqrexy3rGimzsP3OwQGEUjWKcZVSSPT8/k1pPE9hRgOqBy05BfkAzlebdvc3GO5
|
||||||
|
AbZk0NX2sfVHl4dTEXs/hTBCTQ3XmaltumQ9MdL+AQKBgQDg2I5QxBA2UHb8vCtK
|
||||||
|
f31kfG6YQc4MkoslrrMrtJjZqDYaLZPS1ARPSfYRqcc+7GDreuLmw39f8ZECd+2W
|
||||||
|
BYUqzZv9g13R9DY99g0/sINnZGsESwfIdLNNlHvVx2UrD5ybCj4vLhuPsVV7XlWs
|
||||||
|
cpl+rcuBVpqy8UIXifQ/Z3xLvwKBgQDD3CLjuC0mcTO2sIWqEHqVkc8CY2NJA2Qh
|
||||||
|
C78fwpaCqJUUdWnS69QbRGWgkFJL+oO8lQVQ1bXhZLHyQmy7Z5d5olCH6AW4GRnf
|
||||||
|
hBAnKJ+QTm9B6QVWzjUuHuOeCukfiTQbha14pOS9ar3X2QFWjDnzCRrnAxJmoY3H
|
||||||
|
BJATLHhMGwKBgQDSxAy7xt4Pm+O9y8Gk5tcq771X+i9k96V54EZRzMuPFDAK3/h2
|
||||||
|
o4marZD9Q7Hi2P+NHTc+67klvbKZpsPOYkRPOEdmH9M9cPe7oz8OGa9DpwzuDEsy
|
||||||
|
a7p8GZjvbyb1c3/wkWxzG3x4eNnReD9FFHOwHMfr6LvAy4iRuh57pM0NzwKBgDY3
|
||||||
|
1DixnV4M7EHgb7/6O9T3vhRtKujlVWyIcen61etpe4tkTV0kB11c+70M9pstyBYG
|
||||||
|
MqiD4It6coAbvznJnXcAZcaZhivGVxE237nXVwR9kfLu7JlxD+uqhVwUrSAbvR75
|
||||||
|
TGIfU2rUB6We3u30d349wQK+KPPcOQEk1DValBqNAoGBAKfXOXgFBkIVW79fOkup
|
||||||
|
aIZXdEmU3Up61Oo0KDbxsg4l73NnnvuEnNMBTx3nT3KCVIAcQL9MNpLX/Z0HjOn1
|
||||||
|
aiWVtTNq2OFL0V0HueBhbkFiWp551jTS7LjndCYHpUB/B8/wXP0kxHUm8HrQrRvK
|
||||||
|
DhV3zcxsXts1INidXjzzOkPi
|
||||||
|
-----END PRIVATE KEY-----
|
86
test/src/old_slapdtest/certs/server.pem
Normal file
86
test/src/old_slapdtest/certs/server.pem
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 2 (0x2)
|
||||||
|
Signature Algorithm: sha256WithRSAEncryption
|
||||||
|
Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA
|
||||||
|
Validity
|
||||||
|
Not Before: Apr 12 18:52:38 2019 GMT
|
||||||
|
Not After : Mar 1 18:52:38 3019 GMT
|
||||||
|
Subject: C=DE, O=python-ldap, OU=slapd-test, CN=server cert for localhost
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (2048 bit)
|
||||||
|
Modulus:
|
||||||
|
00:ac:06:4d:26:97:71:11:14:9c:a0:23:ab:a3:20:
|
||||||
|
96:04:45:55:38:76:84:d9:65:de:2b:e3:3f:cb:39:
|
||||||
|
e7:f4:87:28:98:88:84:00:73:dc:1e:b5:aa:19:a4:
|
||||||
|
cf:9b:97:81:21:7a:bc:72:f5:20:5a:08:1c:11:cd:
|
||||||
|
e7:db:69:84:33:d9:fd:a1:74:40:db:fa:c2:12:3a:
|
||||||
|
24:e4:7b:57:a2:17:6b:a0:60:d9:52:e7:95:a4:d4:
|
||||||
|
a9:c2:9c:e4:44:f5:b8:dc:48:d2:cf:b2:c7:28:51:
|
||||||
|
2c:c4:d9:b9:c1:4a:85:30:76:25:4a:d7:c6:0e:09:
|
||||||
|
94:7a:19:37:82:f1:e0:37:7a:4b:e5:47:ef:60:f8:
|
||||||
|
50:4e:81:0f:7e:4a:d5:cf:0d:da:be:88:80:06:5d:
|
||||||
|
94:44:24:8b:9a:9a:e9:c4:ae:d4:ce:c3:ce:43:7b:
|
||||||
|
86:05:0a:7c:c6:c3:75:1e:08:84:c3:0f:8c:05:98:
|
||||||
|
8d:88:5a:98:51:17:05:8f:b1:20:f0:50:97:c0:4c:
|
||||||
|
0f:12:fb:b4:66:e2:fc:2b:42:3e:f6:ea:05:03:96:
|
||||||
|
27:13:cc:f5:ca:84:98:d7:8e:3e:97:2f:bc:57:1d:
|
||||||
|
fa:50:c0:c3:e4:04:2a:f1:7a:c2:9c:78:73:09:fa:
|
||||||
|
6a:d1:b2:eb:aa:1d:c8:ae:8b:14:62:83:de:26:20:
|
||||||
|
b1:25
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Basic Constraints: critical
|
||||||
|
CA:FALSE
|
||||||
|
X509v3 Key Usage: critical
|
||||||
|
Digital Signature, Key Encipherment
|
||||||
|
X509v3 Extended Key Usage: critical
|
||||||
|
TLS Web Server Authentication
|
||||||
|
X509v3 Subject Key Identifier:
|
||||||
|
08:D1:86:1B:82:0A:4F:71:31:E4:F5:31:23:CC:67:3B:FA:84:3B:A0
|
||||||
|
X509v3 Authority Key Identifier:
|
||||||
|
keyid:BD:78:D5:4A:F1:90:96:C5:E8:EC:66:49:23:47:03:5F:26:73:86:B2
|
||||||
|
|
||||||
|
X509v3 Subject Alternative Name:
|
||||||
|
DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
|
||||||
|
Signature Algorithm: sha256WithRSAEncryption
|
||||||
|
88:60:af:be:11:c4:aa:dc:9b:f1:e7:14:da:20:aa:6f:2f:06:
|
||||||
|
ae:38:b2:7c:ac:90:81:22:51:7e:cb:26:15:6e:fe:67:98:c1:
|
||||||
|
0d:dc:aa:39:98:2b:d2:cc:3c:ff:1a:92:2f:56:0a:a9:6e:d8:
|
||||||
|
9a:3d:c5:4d:6f:cc:91:2e:e3:4e:bf:22:ab:cb:92:1a:a0:8f:
|
||||||
|
43:cd:82:bc:48:55:c4:95:cf:10:6b:6a:31:19:92:7d:e0:06:
|
||||||
|
05:6f:0b:33:e7:2a:37:42:f9:ec:1b:29:99:e1:58:0c:01:a7:
|
||||||
|
c3:8b:58:71:21:9f:61:8c:a7:fb:b6:7e:32:8b:a9:4e:c7:1f:
|
||||||
|
f6:46:e8:dd:ac:a6:4c:53:f8:4d:93:e4:ec:73:ab:0b:be:98:
|
||||||
|
c5:78:c4:92:c0:4c:78:47:52:2f:93:07:67:20:a4:5a:7f:59:
|
||||||
|
7e:4f:48:53:20:0d:37:bb:06:f8:44:42:64:b4:94:15:43:d1:
|
||||||
|
4c:51:f3:97:1d:2d:cd:db:b9:bb:1a:69:10:89:7d:ae:1d:0d:
|
||||||
|
94:78:45:29:cd:c4:42:67:67:96:05:bf:da:aa:23:65:7b:04:
|
||||||
|
ff:b7:ac:9d:ee:0b:e7:0f:c1:c5:0b:48:fe:0f:d6:3f:d8:b4:
|
||||||
|
77:12:bb:f5:91:4f:43:e6:01:3f:a4:c0:ea:8c:c6:68:99:8e:
|
||||||
|
49:e8:c4:8b
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIID1zCCAr+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU
|
||||||
|
MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV
|
||||||
|
BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwIBcNMTkwNDEyMTg1MjM4WhgPMzAxOTAz
|
||||||
|
MDExODUyMzhaMFwxCzAJBgNVBAYTAkRFMRQwEgYDVQQKDAtweXRob24tbGRhcDET
|
||||||
|
MBEGA1UECwwKc2xhcGQtdGVzdDEiMCAGA1UEAwwZc2VydmVyIGNlcnQgZm9yIGxv
|
||||||
|
Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKwGTSaXcREU
|
||||||
|
nKAjq6MglgRFVTh2hNll3ivjP8s55/SHKJiIhABz3B61qhmkz5uXgSF6vHL1IFoI
|
||||||
|
HBHN59tphDPZ/aF0QNv6whI6JOR7V6IXa6Bg2VLnlaTUqcKc5ET1uNxI0s+yxyhR
|
||||||
|
LMTZucFKhTB2JUrXxg4JlHoZN4Lx4Dd6S+VH72D4UE6BD35K1c8N2r6IgAZdlEQk
|
||||||
|
i5qa6cSu1M7DzkN7hgUKfMbDdR4IhMMPjAWYjYhamFEXBY+xIPBQl8BMDxL7tGbi
|
||||||
|
/CtCPvbqBQOWJxPM9cqEmNeOPpcvvFcd+lDAw+QEKvF6wpx4cwn6atGy66odyK6L
|
||||||
|
FGKD3iYgsSUCAwEAAaOBpzCBpDAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF
|
||||||
|
oDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDATAdBgNVHQ4EFgQUCNGGG4IKT3Ex5PUx
|
||||||
|
I8xnO/qEO6AwHwYDVR0jBBgwFoAUvXjVSvGQlsXo7GZJI0cDXyZzhrIwLAYDVR0R
|
||||||
|
BCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3
|
||||||
|
DQEBCwUAA4IBAQCIYK++EcSq3Jvx5xTaIKpvLwauOLJ8rJCBIlF+yyYVbv5nmMEN
|
||||||
|
3Ko5mCvSzDz/GpIvVgqpbtiaPcVNb8yRLuNOvyKry5IaoI9DzYK8SFXElc8Qa2ox
|
||||||
|
GZJ94AYFbwsz5yo3QvnsGymZ4VgMAafDi1hxIZ9hjKf7tn4yi6lOxx/2RujdrKZM
|
||||||
|
U/hNk+Tsc6sLvpjFeMSSwEx4R1IvkwdnIKRaf1l+T0hTIA03uwb4REJktJQVQ9FM
|
||||||
|
UfOXHS3N27m7GmkQiX2uHQ2UeEUpzcRCZ2eWBb/aqiNlewT/t6yd7gvnD8HFC0j+
|
||||||
|
D9Y/2LR3Erv1kU9D5gE/pMDqjMZomY5J6MSL
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -2,9 +2,53 @@ def testauth_none():
|
||||||
return "some_data_from_none"
|
return "some_data_from_none"
|
||||||
|
|
||||||
|
|
||||||
|
def testauth_subcat_none():
|
||||||
|
return "some_data_from_subcat_none"
|
||||||
|
|
||||||
|
|
||||||
def testauth_default():
|
def testauth_default():
|
||||||
return "some_data_from_default"
|
return "some_data_from_default"
|
||||||
|
|
||||||
|
|
||||||
|
def testauth_subcat_default():
|
||||||
|
return "some_data_from_subcat_default"
|
||||||
|
|
||||||
|
|
||||||
|
def testauth_subcat_post():
|
||||||
|
return "some_data_from_subcat_post"
|
||||||
|
|
||||||
|
|
||||||
def testauth_other_profile():
|
def testauth_other_profile():
|
||||||
return "some_data_from_other_profile"
|
return "some_data_from_other_profile"
|
||||||
|
|
||||||
|
|
||||||
|
def testauth_subcat_other_profile():
|
||||||
|
return "some_data_from_subcat_other_profile"
|
||||||
|
|
||||||
|
|
||||||
|
def testauth_only_api():
|
||||||
|
return "some_data_from_only_api"
|
||||||
|
|
||||||
|
|
||||||
|
def testauth_only_cli():
|
||||||
|
return "some_data_from_only_cli"
|
||||||
|
|
||||||
|
|
||||||
|
def testauth_ldap():
|
||||||
|
return "some_data_from_ldap"
|
||||||
|
|
||||||
|
|
||||||
|
def testauth_with_arg(super_arg):
|
||||||
|
return super_arg
|
||||||
|
|
||||||
|
|
||||||
|
def testauth_with_extra_str_only(only_a_str):
|
||||||
|
return only_a_str
|
||||||
|
|
||||||
|
|
||||||
|
def testauth_with_type_int(only_an_int):
|
||||||
|
return only_an_int
|
||||||
|
|
||||||
|
|
||||||
|
def yoloswag_version(*args, **kwargs):
|
||||||
|
return "666"
|
||||||
|
|
|
@ -3,12 +3,17 @@ import pytest
|
||||||
from moulinette.actionsmap import (
|
from moulinette.actionsmap import (
|
||||||
CommentParameter,
|
CommentParameter,
|
||||||
AskParameter,
|
AskParameter,
|
||||||
|
PasswordParameter,
|
||||||
PatternParameter,
|
PatternParameter,
|
||||||
RequiredParameter,
|
RequiredParameter,
|
||||||
|
ExtraArgumentParser,
|
||||||
ActionsMap,
|
ActionsMap,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from moulinette.interfaces import GLOBAL_SECTION
|
||||||
from moulinette.interfaces import BaseActionsMapParser
|
from moulinette.interfaces import BaseActionsMapParser
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
|
from moulinette import m18n
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -34,6 +39,12 @@ def test_comment_parameter_bad_type(iface):
|
||||||
comment.validate({}, "b")
|
comment.validate({}, "b")
|
||||||
|
|
||||||
|
|
||||||
|
def test_ask_parameter_str_value(iface, caplog):
|
||||||
|
ask = AskParameter(iface)
|
||||||
|
assert ask.validate("a", "a") == "a"
|
||||||
|
assert not len(caplog.messages)
|
||||||
|
|
||||||
|
|
||||||
def test_ask_parameter_bad_bool_value(iface, caplog):
|
def test_ask_parameter_bad_bool_value(iface, caplog):
|
||||||
ask = AskParameter(iface)
|
ask = AskParameter(iface)
|
||||||
assert ask.validate(True, "a") == "a"
|
assert ask.validate(True, "a") == "a"
|
||||||
|
@ -52,6 +63,32 @@ def test_ask_parameter_bad_type(iface):
|
||||||
ask.validate({}, "b")
|
ask.validate({}, "b")
|
||||||
|
|
||||||
|
|
||||||
|
def test_ask_parameter(iface, mocker):
|
||||||
|
ask = AskParameter(iface)
|
||||||
|
arg = ask("foobar", "a", "a")
|
||||||
|
assert arg == "a"
|
||||||
|
|
||||||
|
from moulinette.core import Moulinette18n, MoulinetteSignals
|
||||||
|
|
||||||
|
mocker.patch.object(Moulinette18n, "n", return_value="awesome_test")
|
||||||
|
mocker.patch.object(MoulinetteSignals, "prompt", return_value="awesome_test")
|
||||||
|
arg = ask("foobar", "a", None)
|
||||||
|
assert arg == "awesome_test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_password_parameter(iface, mocker):
|
||||||
|
ask = PasswordParameter(iface)
|
||||||
|
arg = ask("foobar", "a", "a")
|
||||||
|
assert arg == "a"
|
||||||
|
|
||||||
|
from moulinette.core import Moulinette18n, MoulinetteSignals
|
||||||
|
|
||||||
|
mocker.patch.object(Moulinette18n, "n", return_value="awesome_test")
|
||||||
|
mocker.patch.object(MoulinetteSignals, "prompt", return_value="awesome_test")
|
||||||
|
arg = ask("foobar", "a", None)
|
||||||
|
assert arg == "awesome_test"
|
||||||
|
|
||||||
|
|
||||||
def test_pattern_parameter_bad_str_value(iface, caplog):
|
def test_pattern_parameter_bad_str_value(iface, caplog):
|
||||||
pattern = PatternParameter(iface)
|
pattern = PatternParameter(iface)
|
||||||
assert pattern.validate("", "a") == ["", "pattern_not_match"]
|
assert pattern.validate("", "a") == ["", "pattern_not_match"]
|
||||||
|
@ -67,11 +104,56 @@ def test_pattern_parameter_bad_list_len(iface):
|
||||||
pattern.validate(iface, "a")
|
pattern.validate(iface, "a")
|
||||||
|
|
||||||
|
|
||||||
def test_required_paremeter_missing_value(iface):
|
def test_pattern_parameter(iface, caplog, mocker):
|
||||||
|
pattern = PatternParameter(iface)
|
||||||
|
arg = pattern(["foo", "foobar"], "foo_name", "foo_value")
|
||||||
|
assert arg == "foo_value"
|
||||||
|
|
||||||
|
error = "error_message"
|
||||||
|
mocker.patch("moulinette.Moulinette18n.n", return_value=error)
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
pattern(["foo", "message"], "foo_name", "not_match")
|
||||||
|
|
||||||
|
translation = m18n.g("invalid_argument", argument="foo_name", error=error)
|
||||||
|
expected_msg = translation.format(argument="foo_name", error=error)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
assert any("doesn't match pattern" in message for message in caplog.messages)
|
||||||
|
|
||||||
|
|
||||||
|
def test_required_paremeter(iface):
|
||||||
|
required = RequiredParameter(iface)
|
||||||
|
arg = required(True, "a", "a")
|
||||||
|
assert arg == "a"
|
||||||
|
|
||||||
|
assert required.validate(True, "a")
|
||||||
|
assert not required.validate(False, "a")
|
||||||
|
|
||||||
|
|
||||||
|
def test_required_paremeter_bad_type(iface):
|
||||||
|
required = RequiredParameter(iface)
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
required.validate("a", "a")
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
required.validate(1, "a")
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
required.validate([], "a")
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
required.validate({}, "a")
|
||||||
|
|
||||||
|
|
||||||
|
def test_required_paremeter_missing_value(iface, caplog):
|
||||||
required = RequiredParameter(iface)
|
required = RequiredParameter(iface)
|
||||||
with pytest.raises(MoulinetteError) as exception:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
required(True, "a", "")
|
required(True, "a", "")
|
||||||
assert "is required" in str(exception)
|
|
||||||
|
translation = m18n.g("argument_required", argument="a")
|
||||||
|
expected_msg = translation.format(argument="a")
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
assert any("is required" in message for message in caplog.messages)
|
||||||
|
|
||||||
|
|
||||||
def test_actions_map_unknown_authenticator(monkeypatch, tmp_path):
|
def test_actions_map_unknown_authenticator(monkeypatch, tmp_path):
|
||||||
|
@ -83,3 +165,151 @@ def test_actions_map_unknown_authenticator(monkeypatch, tmp_path):
|
||||||
with pytest.raises(ValueError) as exception:
|
with pytest.raises(ValueError) as exception:
|
||||||
amap.get_authenticator_for_profile("unknown")
|
amap.get_authenticator_for_profile("unknown")
|
||||||
assert "Unknown authenticator" in str(exception)
|
assert "Unknown authenticator" in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_extra_argument_parser_add_argument(iface):
|
||||||
|
extra_argument_parse = ExtraArgumentParser(iface)
|
||||||
|
extra_argument_parse.add_argument("Test", "foo", {"ask": "lol"})
|
||||||
|
assert "Test" in extra_argument_parse._extra_params
|
||||||
|
assert "foo" in extra_argument_parse._extra_params["Test"]
|
||||||
|
assert "ask" in extra_argument_parse._extra_params["Test"]["foo"]
|
||||||
|
assert extra_argument_parse._extra_params["Test"]["foo"]["ask"] == "lol"
|
||||||
|
|
||||||
|
extra_argument_parse = ExtraArgumentParser(iface)
|
||||||
|
extra_argument_parse.add_argument(GLOBAL_SECTION, "foo", {"ask": "lol"})
|
||||||
|
assert GLOBAL_SECTION in extra_argument_parse._extra_params
|
||||||
|
assert "foo" in extra_argument_parse._extra_params[GLOBAL_SECTION]
|
||||||
|
assert "ask" in extra_argument_parse._extra_params[GLOBAL_SECTION]["foo"]
|
||||||
|
assert extra_argument_parse._extra_params[GLOBAL_SECTION]["foo"]["ask"] == "lol"
|
||||||
|
|
||||||
|
|
||||||
|
def test_extra_argument_parser_add_argument_bad_arg(iface):
|
||||||
|
extra_argument_parse = ExtraArgumentParser(iface)
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
extra_argument_parse.add_argument(GLOBAL_SECTION, "foo", {"ask": 1})
|
||||||
|
|
||||||
|
translation = m18n.g("error_see_log")
|
||||||
|
expected_msg = translation.format()
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
extra_argument_parse = ExtraArgumentParser(iface)
|
||||||
|
extra_argument_parse.add_argument(GLOBAL_SECTION, "foo", {"error": 1})
|
||||||
|
|
||||||
|
assert GLOBAL_SECTION in extra_argument_parse._extra_params
|
||||||
|
assert "foo" in extra_argument_parse._extra_params[GLOBAL_SECTION]
|
||||||
|
assert not len(extra_argument_parse._extra_params[GLOBAL_SECTION]["foo"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_extra_argument_parser_parse_args(iface, mocker):
|
||||||
|
extra_argument_parse = ExtraArgumentParser(iface)
|
||||||
|
extra_argument_parse.add_argument(GLOBAL_SECTION, "foo", {"ask": "lol"})
|
||||||
|
extra_argument_parse.add_argument(GLOBAL_SECTION, "foo2", {"ask": "lol2"})
|
||||||
|
extra_argument_parse.add_argument(
|
||||||
|
GLOBAL_SECTION, "bar", {"password": "lul", "ask": "lul"}
|
||||||
|
)
|
||||||
|
|
||||||
|
args = extra_argument_parse.parse_args(
|
||||||
|
GLOBAL_SECTION, {"foo": 1, "foo2": ["a", "b", {"foobar": True}], "bar": "rab"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "foo" in args
|
||||||
|
assert args["foo"] == 1
|
||||||
|
|
||||||
|
assert "foo2" in args
|
||||||
|
assert args["foo2"] == ["a", "b", {"foobar": True}]
|
||||||
|
|
||||||
|
assert "bar" in args
|
||||||
|
assert args["bar"] == "rab"
|
||||||
|
|
||||||
|
|
||||||
|
def test_actions_map_api():
|
||||||
|
from moulinette.interfaces.api import ActionsMapParser
|
||||||
|
|
||||||
|
amap = ActionsMap(ActionsMapParser, use_cache=False)
|
||||||
|
|
||||||
|
assert amap.parser.global_conf["authenticate"] == "all"
|
||||||
|
assert "default" in amap.parser.global_conf["authenticator"]
|
||||||
|
assert "yoloswag" in amap.parser.global_conf["authenticator"]
|
||||||
|
assert ("GET", "/test-auth/default") in amap.parser.routes
|
||||||
|
assert ("POST", "/test-auth/subcat/post") in amap.parser.routes
|
||||||
|
|
||||||
|
amap.generate_cache()
|
||||||
|
|
||||||
|
amap = ActionsMap(ActionsMapParser, use_cache=True)
|
||||||
|
|
||||||
|
assert amap.parser.global_conf["authenticate"] == "all"
|
||||||
|
assert "default" in amap.parser.global_conf["authenticator"]
|
||||||
|
assert "yoloswag" in amap.parser.global_conf["authenticator"]
|
||||||
|
assert ("GET", "/test-auth/default") in amap.parser.routes
|
||||||
|
assert ("POST", "/test-auth/subcat/post") in amap.parser.routes
|
||||||
|
|
||||||
|
|
||||||
|
def test_actions_map_import_error(mocker):
|
||||||
|
from moulinette.interfaces.api import ActionsMapParser
|
||||||
|
|
||||||
|
amap = ActionsMap(ActionsMapParser)
|
||||||
|
|
||||||
|
from moulinette.core import MoulinetteLock
|
||||||
|
|
||||||
|
mocker.patch.object(MoulinetteLock, "_is_son_of", return_value=False)
|
||||||
|
|
||||||
|
mocker.patch("__builtin__.__import__", side_effect=ImportError)
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
amap.process({}, timeout=30, route=("GET", "/test-auth/none"))
|
||||||
|
|
||||||
|
mocker.stopall()
|
||||||
|
translation = m18n.g("error_see_log")
|
||||||
|
expected_msg = translation.format()
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_actions_map_cli():
|
||||||
|
from moulinette.interfaces.cli import ActionsMapParser
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
|
parser.add_argument(
|
||||||
|
"--debug",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Log and print debug messages",
|
||||||
|
)
|
||||||
|
amap = ActionsMap(
|
||||||
|
ActionsMapParser, use_cache=False, parser_kwargs={"top_parser": parser}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert amap.parser.global_conf["authenticate"] == "all"
|
||||||
|
assert "default" in amap.parser.global_conf["authenticator"]
|
||||||
|
assert "yoloswag" in amap.parser.global_conf["authenticator"]
|
||||||
|
assert "testauth" in amap.parser._subparsers.choices
|
||||||
|
assert "none" in amap.parser._subparsers.choices["testauth"]._actions[1].choices
|
||||||
|
assert "subcat" in amap.parser._subparsers.choices["testauth"]._actions[1].choices
|
||||||
|
assert (
|
||||||
|
"default"
|
||||||
|
in amap.parser._subparsers.choices["testauth"]
|
||||||
|
._actions[1]
|
||||||
|
.choices["subcat"]
|
||||||
|
._actions[1]
|
||||||
|
.choices
|
||||||
|
)
|
||||||
|
|
||||||
|
amap.generate_cache()
|
||||||
|
|
||||||
|
amap = ActionsMap(
|
||||||
|
ActionsMapParser, use_cache=True, parser_kwargs={"top_parser": parser}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert amap.parser.global_conf["authenticate"] == "all"
|
||||||
|
assert "default" in amap.parser.global_conf["authenticator"]
|
||||||
|
assert "yoloswag" in amap.parser.global_conf["authenticator"]
|
||||||
|
assert "testauth" in amap.parser._subparsers.choices
|
||||||
|
assert "none" in amap.parser._subparsers.choices["testauth"]._actions[1].choices
|
||||||
|
assert "subcat" in amap.parser._subparsers.choices["testauth"]._actions[1].choices
|
||||||
|
assert (
|
||||||
|
"default"
|
||||||
|
in amap.parser._subparsers.choices["testauth"]
|
||||||
|
._actions[1]
|
||||||
|
.choices["subcat"]
|
||||||
|
._actions[1]
|
||||||
|
.choices
|
||||||
|
)
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import os
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from moulinette import MoulinetteError
|
||||||
|
from moulinette import m18n
|
||||||
|
|
||||||
|
|
||||||
def login(webapi, csrf=False, profile=None, status=200):
|
class TestAuthAPI:
|
||||||
|
def login(self, webapi, csrf=False, profile=None, status=200, password="default"):
|
||||||
data = {"password": "Yoloswag"}
|
data = {"password": password}
|
||||||
if profile:
|
if profile:
|
||||||
data["profile"] = profile
|
data["profile"] = profile
|
||||||
|
|
||||||
|
@ -14,26 +18,51 @@ def login(webapi, csrf=False, profile=None, status=200):
|
||||||
headers=None if csrf else {"X-Requested-With": ""},
|
headers=None if csrf else {"X-Requested-With": ""},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_request_no_auth_needed(self, moulinette_webapi):
|
||||||
def test_request_no_auth_needed(moulinette_webapi):
|
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
moulinette_webapi.get("/test-auth/none", status=200).text
|
moulinette_webapi.get("/test-auth/none", status=200).text
|
||||||
== '"some_data_from_none"'
|
== '"some_data_from_none"'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_request_no_auth_needed_subcategories(self, moulinette_webapi):
|
||||||
|
assert (
|
||||||
|
moulinette_webapi.get("/test-auth/subcat/none", status=200).text
|
||||||
|
== '"some_data_from_subcat_none"'
|
||||||
|
)
|
||||||
|
|
||||||
def test_request_with_auth_but_not_logged(moulinette_webapi):
|
def test_request_with_auth_but_not_logged(self, moulinette_webapi):
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
moulinette_webapi.get("/test-auth/default", status=401).text
|
moulinette_webapi.get("/test-auth/default", status=401).text
|
||||||
== "Authentication required"
|
== "Authentication required"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_request_with_auth_subcategories_but_not_logged(self, moulinette_webapi):
|
||||||
|
assert (
|
||||||
|
moulinette_webapi.get("/test-auth/subcat/default", status=401).text
|
||||||
|
== "Authentication required"
|
||||||
|
)
|
||||||
|
|
||||||
def test_login(moulinette_webapi):
|
def test_request_not_logged_only_api(self, moulinette_webapi):
|
||||||
|
assert (
|
||||||
|
moulinette_webapi.get("/test-auth/only-api", status=401).text
|
||||||
|
== "Authentication required"
|
||||||
|
)
|
||||||
|
|
||||||
assert login(moulinette_webapi).text == "Logged in"
|
def test_request_only_api(self, moulinette_webapi):
|
||||||
|
self.login(moulinette_webapi)
|
||||||
|
assert (
|
||||||
|
moulinette_webapi.get("/test-auth/only-api", status=200).text
|
||||||
|
== '"some_data_from_only_api"'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_request_not_logged_only_cli(self, moulinette_webapi):
|
||||||
|
assert (
|
||||||
|
moulinette_webapi.get("/test-auth/only-cli", status=200).text
|
||||||
|
== '"some_data_from_only_cli"'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_login(self, moulinette_webapi):
|
||||||
|
assert self.login(moulinette_webapi).text == "Logged in"
|
||||||
|
|
||||||
assert "session.id" in moulinette_webapi.cookies
|
assert "session.id" in moulinette_webapi.cookies
|
||||||
assert "session.tokens" in moulinette_webapi.cookies
|
assert "session.tokens" in moulinette_webapi.cookies
|
||||||
|
@ -43,40 +72,49 @@ def test_login(moulinette_webapi):
|
||||||
cache_session_default
|
cache_session_default
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_login_bad_password(self, moulinette_webapi):
|
||||||
|
assert (
|
||||||
|
self.login(moulinette_webapi, password="Bad Password", status=401).text
|
||||||
|
== "Invalid password"
|
||||||
|
)
|
||||||
|
|
||||||
def test_login_csrf_attempt(moulinette_webapi):
|
assert "session.id" not in moulinette_webapi.cookies
|
||||||
|
assert "session.tokens" not in moulinette_webapi.cookies
|
||||||
|
|
||||||
|
def test_login_csrf_attempt(self, moulinette_webapi):
|
||||||
# C.f.
|
# C.f.
|
||||||
# https://security.stackexchange.com/a/58308
|
# https://security.stackexchange.com/a/58308
|
||||||
# https://stackoverflow.com/a/22533680
|
# https://stackoverflow.com/a/22533680
|
||||||
|
|
||||||
assert "CSRF protection" in login(moulinette_webapi, csrf=True, status=403).text
|
assert (
|
||||||
|
"CSRF protection"
|
||||||
|
in self.login(moulinette_webapi, csrf=True, status=403).text
|
||||||
|
)
|
||||||
assert not any(c.name == "session.id" for c in moulinette_webapi.cookiejar)
|
assert not any(c.name == "session.id" for c in moulinette_webapi.cookiejar)
|
||||||
assert not any(c.name == "session.tokens" for c in moulinette_webapi.cookiejar)
|
assert not any(c.name == "session.tokens" for c in moulinette_webapi.cookiejar)
|
||||||
|
|
||||||
|
def test_login_then_legit_request_without_cookies(self, moulinette_webapi):
|
||||||
def test_login_then_legit_request_without_cookies(moulinette_webapi):
|
self.login(moulinette_webapi)
|
||||||
|
|
||||||
login(moulinette_webapi)
|
|
||||||
|
|
||||||
moulinette_webapi.cookiejar.clear()
|
moulinette_webapi.cookiejar.clear()
|
||||||
|
|
||||||
moulinette_webapi.get("/test-auth/default", status=401)
|
moulinette_webapi.get("/test-auth/default", status=401)
|
||||||
|
|
||||||
|
def test_login_then_legit_request(self, moulinette_webapi):
|
||||||
def test_login_then_legit_request(moulinette_webapi):
|
self.login(moulinette_webapi)
|
||||||
|
|
||||||
login(moulinette_webapi)
|
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
moulinette_webapi.get("/test-auth/default", status=200).text
|
moulinette_webapi.get("/test-auth/default", status=200).text
|
||||||
== '"some_data_from_default"'
|
== '"some_data_from_default"'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
moulinette_webapi.get("/test-auth/subcat/default", status=200).text
|
||||||
|
== '"some_data_from_subcat_default"'
|
||||||
|
)
|
||||||
|
|
||||||
def test_login_then_logout(moulinette_webapi):
|
def test_login_then_logout(self, moulinette_webapi):
|
||||||
|
self.login(moulinette_webapi)
|
||||||
login(moulinette_webapi)
|
|
||||||
|
|
||||||
moulinette_webapi.get("/logout", status=200)
|
moulinette_webapi.get("/logout", status=200)
|
||||||
|
|
||||||
|
@ -89,3 +127,217 @@ def test_login_then_logout(moulinette_webapi):
|
||||||
moulinette_webapi.get("/test-auth/default", status=401).text
|
moulinette_webapi.get("/test-auth/default", status=401).text
|
||||||
== "Authentication required"
|
== "Authentication required"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_login_other_profile(self, moulinette_webapi):
|
||||||
|
self.login(moulinette_webapi, profile="yoloswag", password="yoloswag")
|
||||||
|
|
||||||
|
assert "session.id" in moulinette_webapi.cookies
|
||||||
|
assert "session.tokens" in moulinette_webapi.cookies
|
||||||
|
|
||||||
|
cache_session_default = (
|
||||||
|
os.environ["MOULINETTE_CACHE_DIR"] + "/session/yoloswag/"
|
||||||
|
)
|
||||||
|
assert moulinette_webapi.cookies["session.id"] + ".asc" in os.listdir(
|
||||||
|
cache_session_default
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_login_wrong_profile(self, moulinette_webapi):
|
||||||
|
self.login(moulinette_webapi)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
moulinette_webapi.get("/test-auth/other-profile", status=401).text
|
||||||
|
== "Authentication required"
|
||||||
|
)
|
||||||
|
|
||||||
|
moulinette_webapi.get("/logout", status=200)
|
||||||
|
|
||||||
|
self.login(moulinette_webapi, profile="yoloswag", password="yoloswag")
|
||||||
|
|
||||||
|
assert (
|
||||||
|
moulinette_webapi.get("/test-auth/default", status=401).text
|
||||||
|
== "Authentication required"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_login_ldap(self, moulinette_webapi, ldap_server, mocker):
|
||||||
|
mocker.patch(
|
||||||
|
"moulinette.authenticators.ldap.Authenticator._get_uri",
|
||||||
|
return_value=ldap_server.uri,
|
||||||
|
)
|
||||||
|
self.login(moulinette_webapi, profile="ldap", password="yunohost")
|
||||||
|
|
||||||
|
assert (
|
||||||
|
moulinette_webapi.get("/test-auth/ldap", status=200).text
|
||||||
|
== '"some_data_from_ldap"'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_request_with_arg(self, moulinette_webapi, capsys):
|
||||||
|
self.login(moulinette_webapi)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
moulinette_webapi.get("/test-auth/with_arg/yoloswag", status=200).text
|
||||||
|
== '"yoloswag"'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_request_arg_with_extra(self, moulinette_webapi, caplog, mocker):
|
||||||
|
self.login(moulinette_webapi)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
moulinette_webapi.get(
|
||||||
|
"/test-auth/with_extra_str_only/YoLoSwAg", status=200
|
||||||
|
).text
|
||||||
|
== '"YoLoSwAg"'
|
||||||
|
)
|
||||||
|
|
||||||
|
error = "error_message"
|
||||||
|
mocker.patch("moulinette.Moulinette18n.n", return_value=error)
|
||||||
|
|
||||||
|
moulinette_webapi.get("/test-auth/with_extra_str_only/12345", status=400)
|
||||||
|
|
||||||
|
assert any("doesn't match pattern" in message for message in caplog.messages)
|
||||||
|
|
||||||
|
def test_request_arg_with_type(self, moulinette_webapi, caplog, mocker):
|
||||||
|
self.login(moulinette_webapi)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
moulinette_webapi.get("/test-auth/with_type_int/12345", status=200).text
|
||||||
|
== "12345"
|
||||||
|
)
|
||||||
|
|
||||||
|
error = "error_message"
|
||||||
|
mocker.patch("moulinette.Moulinette18n.g", return_value=error)
|
||||||
|
moulinette_webapi.get("/test-auth/with_type_int/yoloswag", status=400)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAuthCLI:
|
||||||
|
def test_login(self, moulinette_cli, capsys, mocker):
|
||||||
|
mocker.patch("getpass.getpass", return_value="default")
|
||||||
|
moulinette_cli.run(["testauth", "default"], output_as="plain")
|
||||||
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
assert "some_data_from_default" in message.out
|
||||||
|
|
||||||
|
moulinette_cli.run(
|
||||||
|
["testauth", "default"], output_as="plain", password="default"
|
||||||
|
)
|
||||||
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
assert "some_data_from_default" in message.out
|
||||||
|
|
||||||
|
def test_login_bad_password(self, moulinette_cli, capsys, mocker):
|
||||||
|
with pytest.raises(MoulinetteError):
|
||||||
|
moulinette_cli.run(
|
||||||
|
["testauth", "default"], output_as="plain", password="Bad Password"
|
||||||
|
)
|
||||||
|
|
||||||
|
mocker.patch("getpass.getpass", return_value="Bad Password")
|
||||||
|
with pytest.raises(MoulinetteError):
|
||||||
|
moulinette_cli.run(["testauth", "default"], output_as="plain")
|
||||||
|
|
||||||
|
def test_login_wrong_profile(self, moulinette_cli, mocker):
|
||||||
|
mocker.patch("getpass.getpass", return_value="default")
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
moulinette_cli.run(["testauth", "other-profile"], output_as="none")
|
||||||
|
|
||||||
|
translation = m18n.g("invalid_password")
|
||||||
|
expected_msg = translation.format()
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
moulinette_cli.run(
|
||||||
|
["testauth", "default"], output_as="none", password="yoloswag"
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_msg = translation.format()
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
def test_request_no_auth_needed(self, capsys, moulinette_cli):
|
||||||
|
moulinette_cli.run(["testauth", "none"], output_as="plain")
|
||||||
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
assert "some_data_from_none" in message.out
|
||||||
|
|
||||||
|
def test_request_not_logged_only_api(self, capsys, moulinette_cli):
|
||||||
|
moulinette_cli.run(["testauth", "only-api"], output_as="plain")
|
||||||
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
assert "some_data_from_only_api" in message.out
|
||||||
|
|
||||||
|
def test_request_only_cli(self, capsys, moulinette_cli, mocker):
|
||||||
|
mocker.patch("getpass.getpass", return_value="default")
|
||||||
|
moulinette_cli.run(["testauth", "only-cli"], output_as="plain")
|
||||||
|
|
||||||
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
assert "some_data_from_only_cli" in message.out
|
||||||
|
|
||||||
|
def test_request_not_logged_only_cli(self, capsys, moulinette_cli, mocker):
|
||||||
|
mocker.patch("getpass.getpass")
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
moulinette_cli.run(["testauth", "only-cli"], output_as="plain")
|
||||||
|
|
||||||
|
message = capsys.readouterr()
|
||||||
|
assert "some_data_from_only_cli" not in message.out
|
||||||
|
|
||||||
|
translation = m18n.g("invalid_password")
|
||||||
|
expected_msg = translation.format()
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
def test_request_with_callback(self, moulinette_cli, capsys, mocker):
|
||||||
|
mocker.patch("getpass.getpass", return_value="default")
|
||||||
|
moulinette_cli.run(["--version"], output_as="plain")
|
||||||
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
assert "666" in message.out
|
||||||
|
|
||||||
|
moulinette_cli.run(["-v"], output_as="plain")
|
||||||
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
assert "666" in message.out
|
||||||
|
|
||||||
|
with pytest.raises(MoulinetteError):
|
||||||
|
moulinette_cli.run(["--wersion"], output_as="plain")
|
||||||
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
assert "cannot get value from callback method" in message.err
|
||||||
|
|
||||||
|
def test_request_with_arg(self, moulinette_cli, capsys, mocker):
|
||||||
|
mocker.patch("getpass.getpass", return_value="default")
|
||||||
|
moulinette_cli.run(["testauth", "with_arg", "yoloswag"], output_as="plain")
|
||||||
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
assert "yoloswag" in message.out
|
||||||
|
|
||||||
|
def test_request_arg_with_extra(self, moulinette_cli, capsys, mocker):
|
||||||
|
mocker.patch("getpass.getpass", return_value="default")
|
||||||
|
moulinette_cli.run(
|
||||||
|
["testauth", "with_extra_str_only", "YoLoSwAg"], output_as="plain"
|
||||||
|
)
|
||||||
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
assert "YoLoSwAg" in message.out
|
||||||
|
|
||||||
|
error = "error_message"
|
||||||
|
mocker.patch("moulinette.Moulinette18n.n", return_value=error)
|
||||||
|
with pytest.raises(MoulinetteError):
|
||||||
|
moulinette_cli.run(
|
||||||
|
["testauth", "with_extra_str_only", "12345"], output_as="plain"
|
||||||
|
)
|
||||||
|
|
||||||
|
message = capsys.readouterr()
|
||||||
|
assert "doesn't match pattern" in message.err
|
||||||
|
|
||||||
|
def test_request_arg_with_type(self, moulinette_cli, capsys, mocker):
|
||||||
|
mocker.patch("getpass.getpass", return_value="default")
|
||||||
|
moulinette_cli.run(["testauth", "with_type_int", "12345"], output_as="plain")
|
||||||
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
assert "12345" in message.out
|
||||||
|
|
||||||
|
mocker.patch("sys.exit")
|
||||||
|
with pytest.raises(MoulinetteError):
|
||||||
|
moulinette_cli.run(
|
||||||
|
["testauth", "with_type_int", "yoloswag"], output_as="plain"
|
||||||
|
)
|
||||||
|
|
||||||
|
message = capsys.readouterr()
|
||||||
|
assert "invalid int value" in message.err
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import pwd
|
||||||
|
import grp
|
||||||
|
|
||||||
from moulinette import m18n
|
from moulinette import m18n
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
|
@ -8,9 +10,16 @@ from moulinette.utils.filesystem import (
|
||||||
append_to_file,
|
append_to_file,
|
||||||
read_file,
|
read_file,
|
||||||
read_json,
|
read_json,
|
||||||
|
read_yaml,
|
||||||
|
read_toml,
|
||||||
|
read_ldif,
|
||||||
rm,
|
rm,
|
||||||
write_to_file,
|
write_to_file,
|
||||||
write_to_json,
|
write_to_json,
|
||||||
|
write_to_yaml,
|
||||||
|
mkdir,
|
||||||
|
chown,
|
||||||
|
chmod,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,6 +51,18 @@ def test_read_file_cannot_read_ioerror(test_file, mocker):
|
||||||
assert expected_msg in str(exception)
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_file_cannot_read_exception(test_file, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
mocker.patch("__builtin__.open", side_effect=Exception(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
read_file(str(test_file))
|
||||||
|
|
||||||
|
translation = m18n.g("unknown_error_reading_file", file=str(test_file), error=error)
|
||||||
|
expected_msg = translation.format(file=str(test_file), error=error)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
def test_read_json(test_json):
|
def test_read_json(test_json):
|
||||||
content = read_json(str(test_json))
|
content = read_json(str(test_json))
|
||||||
assert "foo" in content.keys()
|
assert "foo" in content.keys()
|
||||||
|
@ -60,6 +81,82 @@ def test_read_json_cannot_read(test_json, mocker):
|
||||||
assert expected_msg in str(exception)
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_yaml(test_yaml):
|
||||||
|
content = read_yaml(str(test_yaml))
|
||||||
|
assert "foo" in content.keys()
|
||||||
|
assert content["foo"] == "bar"
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_yaml_cannot_read(test_yaml, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
mocker.patch("yaml.safe_load", side_effect=Exception(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
read_yaml(str(test_yaml))
|
||||||
|
|
||||||
|
translation = m18n.g("corrupted_yaml", ressource=str(test_yaml), error=error)
|
||||||
|
expected_msg = translation.format(ressource=str(test_yaml), error=error)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_toml(test_toml):
|
||||||
|
content = read_toml(str(test_toml))
|
||||||
|
assert "foo" in content.keys()
|
||||||
|
assert content["foo"] == "bar"
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_toml_cannot_read(test_toml, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
mocker.patch("toml.loads", side_effect=Exception(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
read_toml(str(test_toml))
|
||||||
|
|
||||||
|
translation = m18n.g("corrupted_toml", ressource=str(test_toml), error=error)
|
||||||
|
expected_msg = translation.format(ressource=str(test_toml), error=error)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_ldif(test_ldif):
|
||||||
|
dn, entry = read_ldif(str(test_ldif))[0]
|
||||||
|
|
||||||
|
assert dn == "mail=alice@example.com"
|
||||||
|
assert entry["mail"] == ["alice@example.com"]
|
||||||
|
assert entry["objectclass"] == ["top", "person"]
|
||||||
|
assert entry["cn"] == ["Alice Alison"]
|
||||||
|
|
||||||
|
dn, entry = read_ldif(str(test_ldif), ["objectclass"])[0]
|
||||||
|
|
||||||
|
assert dn == "mail=alice@example.com"
|
||||||
|
assert entry["mail"] == ["alice@example.com"]
|
||||||
|
assert "objectclass" not in entry
|
||||||
|
assert entry["cn"] == ["Alice Alison"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_ldif_cannot_ioerror(test_ldif, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
mocker.patch("__builtin__.open", side_effect=IOError(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
read_ldif(str(test_ldif))
|
||||||
|
|
||||||
|
translation = m18n.g("cannot_open_file", file=str(test_ldif), error=error)
|
||||||
|
expected_msg = translation.format(file=str(test_ldif), error=error)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_ldif_cannot_exception(test_ldif, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
mocker.patch("__builtin__.open", side_effect=Exception(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
read_ldif(str(test_ldif))
|
||||||
|
|
||||||
|
translation = m18n.g("unknown_error_reading_file", file=str(test_ldif), error=error)
|
||||||
|
expected_msg = translation.format(file=str(test_ldif), error=error)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
def test_write_to_existing_file(test_file):
|
def test_write_to_existing_file(test_file):
|
||||||
write_to_file(str(test_file), "yolo\nswag")
|
write_to_file(str(test_file), "yolo\nswag")
|
||||||
assert read_file(str(test_file)) == "yolo\nswag"
|
assert read_file(str(test_file)) == "yolo\nswag"
|
||||||
|
@ -86,6 +183,18 @@ def test_write_to_existing_file_bad_perms(test_file, mocker):
|
||||||
assert expected_msg in str(exception)
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_to_file_exception(test_file, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
mocker.patch("__builtin__.open", side_effect=Exception(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
write_to_file(str(test_file), "yolo\nswag")
|
||||||
|
|
||||||
|
translation = m18n.g("error_writing_file", file=str(test_file), error=error)
|
||||||
|
expected_msg = translation.format(file=str(test_file), error=error)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
def test_write_cannot_write_folder(tmp_path):
|
def test_write_cannot_write_folder(tmp_path):
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(AssertionError):
|
||||||
write_to_file(str(tmp_path), "yolo\nswag")
|
write_to_file(str(tmp_path), "yolo\nswag")
|
||||||
|
@ -115,7 +224,7 @@ def test_append_to_new_file(tmp_path):
|
||||||
assert read_file(str(new_file)) == "yolo\nswag"
|
assert read_file(str(new_file)) == "yolo\nswag"
|
||||||
|
|
||||||
|
|
||||||
def text_write_dict_to_json(tmp_path):
|
def test_write_dict_to_json(tmp_path):
|
||||||
new_file = tmp_path / "newfile.json"
|
new_file = tmp_path / "newfile.json"
|
||||||
|
|
||||||
dummy_dict = {"foo": 42, "bar": ["a", "b", "c"]}
|
dummy_dict = {"foo": 42, "bar": ["a", "b", "c"]}
|
||||||
|
@ -129,6 +238,34 @@ def text_write_dict_to_json(tmp_path):
|
||||||
assert _json["bar"] == ["a", "b", "c"]
|
assert _json["bar"] == ["a", "b", "c"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_json_to_existing_file_bad_perms(test_file, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
dummy_dict = {"foo": 42, "bar": ["a", "b", "c"]}
|
||||||
|
|
||||||
|
mocker.patch("__builtin__.open", side_effect=IOError(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
write_to_json(str(test_file), dummy_dict)
|
||||||
|
|
||||||
|
translation = m18n.g("cannot_write_file", file=str(test_file), error=error)
|
||||||
|
expected_msg = translation.format(file=str(test_file), error=error)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_json_to_file_exception(test_file, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
dummy_dict = {"foo": 42, "bar": ["a", "b", "c"]}
|
||||||
|
|
||||||
|
mocker.patch("__builtin__.open", side_effect=Exception(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
write_to_json(str(test_file), dummy_dict)
|
||||||
|
|
||||||
|
translation = m18n.g("error_writing_file", file=str(test_file), error=error)
|
||||||
|
expected_msg = translation.format(file=str(test_file), error=error)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
def text_write_list_to_json(tmp_path):
|
def text_write_list_to_json(tmp_path):
|
||||||
new_file = tmp_path / "newfile.json"
|
new_file = tmp_path / "newfile.json"
|
||||||
|
|
||||||
|
@ -156,6 +293,218 @@ def test_write_json_cannot_write_to_non_existant_folder():
|
||||||
write_to_json("/toto/test.json", ["a", "b"])
|
write_to_json("/toto/test.json", ["a", "b"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_dict_to_yaml(tmp_path):
|
||||||
|
new_file = tmp_path / "newfile.yaml"
|
||||||
|
|
||||||
|
dummy_dict = {"foo": 42, "bar": ["a", "b", "c"]}
|
||||||
|
write_to_yaml(str(new_file), dummy_dict)
|
||||||
|
_yaml = read_yaml(str(new_file))
|
||||||
|
|
||||||
|
assert "foo" in _yaml.keys()
|
||||||
|
assert "bar" in _yaml.keys()
|
||||||
|
|
||||||
|
assert _yaml["foo"] == 42
|
||||||
|
assert _yaml["bar"] == ["a", "b", "c"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_yaml_to_existing_file_bad_perms(test_file, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
dummy_dict = {"foo": 42, "bar": ["a", "b", "c"]}
|
||||||
|
|
||||||
|
mocker.patch("__builtin__.open", side_effect=IOError(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
write_to_yaml(str(test_file), dummy_dict)
|
||||||
|
|
||||||
|
translation = m18n.g("cannot_write_file", file=str(test_file), error=error)
|
||||||
|
expected_msg = translation.format(file=str(test_file), error=error)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_yaml_to_file_exception(test_file, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
dummy_dict = {"foo": 42, "bar": ["a", "b", "c"]}
|
||||||
|
|
||||||
|
mocker.patch("__builtin__.open", side_effect=Exception(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
write_to_yaml(str(test_file), dummy_dict)
|
||||||
|
|
||||||
|
translation = m18n.g("error_writing_file", file=str(test_file), error=error)
|
||||||
|
expected_msg = translation.format(file=str(test_file), error=error)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def text_write_list_to_yaml(tmp_path):
|
||||||
|
new_file = tmp_path / "newfile.yaml"
|
||||||
|
|
||||||
|
dummy_list = ["foo", "bar", "baz"]
|
||||||
|
write_to_yaml(str(new_file), dummy_list)
|
||||||
|
|
||||||
|
_yaml = read_yaml(str(new_file))
|
||||||
|
assert _yaml == ["foo", "bar", "baz"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_to_yaml_bad_perms(test_yaml, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
mocker.patch("__builtin__.open", side_effect=IOError(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
write_to_yaml(str(test_yaml), {"a": 1})
|
||||||
|
|
||||||
|
translation = m18n.g("cannot_write_file", file=str(test_yaml), error=error)
|
||||||
|
expected_msg = translation.format(file=str(test_yaml), error=error)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_yaml_cannot_write_to_non_existant_folder():
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
write_to_yaml("/toto/test.yaml", ["a", "b"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_mkdir(tmp_path):
|
||||||
|
new_path = tmp_path / "new_folder"
|
||||||
|
mkdir(str(new_path))
|
||||||
|
|
||||||
|
assert os.path.isdir(str(new_path))
|
||||||
|
assert oct(os.stat(str(new_path)).st_mode & 0o777) == oct(0o777)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mkdir_with_permission(tmp_path, mocker):
|
||||||
|
new_path = tmp_path / "new_folder"
|
||||||
|
permission = 0o700
|
||||||
|
mkdir(str(new_path), mode=permission)
|
||||||
|
|
||||||
|
assert os.path.isdir(str(new_path))
|
||||||
|
assert oct(os.stat(str(new_path)).st_mode & 0o777) == oct(permission)
|
||||||
|
|
||||||
|
new_path = tmp_path / "new_parent2" / "new_folder"
|
||||||
|
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
mkdir(str(new_path), parents=True, mode=0o000)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mkdir_with_parent(tmp_path):
|
||||||
|
new_path = tmp_path / "new_folder"
|
||||||
|
mkdir(str(new_path) + "/", parents=True)
|
||||||
|
|
||||||
|
assert os.path.isdir(str(new_path))
|
||||||
|
|
||||||
|
new_path = tmp_path / "new_parent" / "new_folder"
|
||||||
|
mkdir(str(new_path), parents=True)
|
||||||
|
|
||||||
|
assert os.path.isdir(str(new_path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_mkdir_existing_folder(tmp_path):
|
||||||
|
new_path = tmp_path / "new_folder"
|
||||||
|
os.makedirs(str(new_path))
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
mkdir(str(new_path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_chown(test_file):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
chown(str(test_file))
|
||||||
|
|
||||||
|
current_uid = os.getuid()
|
||||||
|
current_gid = os.getgid()
|
||||||
|
chown(str(test_file), current_uid, current_gid)
|
||||||
|
|
||||||
|
assert os.stat(str(test_file)).st_uid == current_uid
|
||||||
|
assert os.stat(str(test_file)).st_gid == current_gid
|
||||||
|
|
||||||
|
current_gid = os.getgid()
|
||||||
|
chown(str(test_file), uid=None, gid=current_gid)
|
||||||
|
|
||||||
|
assert os.stat(str(test_file)).st_gid == current_gid
|
||||||
|
|
||||||
|
current_uid = pwd.getpwuid(os.getuid())[0]
|
||||||
|
current_gid = grp.getgrgid(os.getgid())[0]
|
||||||
|
chown(str(test_file), current_uid, current_gid)
|
||||||
|
|
||||||
|
assert os.stat(str(test_file)).st_uid == os.getuid()
|
||||||
|
assert os.stat(str(test_file)).st_gid == os.getgid()
|
||||||
|
|
||||||
|
fake_user = "nousrlol"
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
chown(str(test_file), fake_user)
|
||||||
|
|
||||||
|
translation = m18n.g("unknown_user", user=fake_user)
|
||||||
|
expected_msg = translation.format(user=fake_user)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
fake_grp = "nogrplol"
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
chown(str(test_file), gid=fake_grp)
|
||||||
|
|
||||||
|
translation = m18n.g("unknown_group", group=fake_grp)
|
||||||
|
expected_msg = translation.format(group=fake_grp)
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_chown_recursive(test_file):
|
||||||
|
current_uid = os.getuid()
|
||||||
|
dirname = os.path.dirname(str(test_file))
|
||||||
|
mkdir(os.path.join(dirname, "new_dir"))
|
||||||
|
chown(str(dirname), current_uid, recursive=True)
|
||||||
|
|
||||||
|
assert os.stat(str(dirname)).st_uid == current_uid
|
||||||
|
|
||||||
|
|
||||||
|
def test_chown_exception(test_file, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
mocker.patch("os.chown", side_effect=Exception(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
chown(str(test_file), 1)
|
||||||
|
|
||||||
|
translation = m18n.g(
|
||||||
|
"error_changing_file_permissions", path=test_file, error=str(error)
|
||||||
|
)
|
||||||
|
expected_msg = translation.format(path=test_file, error=str(error))
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_chmod(test_file):
|
||||||
|
permission = 0o723
|
||||||
|
chmod(str(test_file), permission)
|
||||||
|
|
||||||
|
assert oct(os.stat(str(test_file)).st_mode & 0o777) == oct(permission)
|
||||||
|
|
||||||
|
dirname = os.path.dirname(str(test_file))
|
||||||
|
permission = 0o722
|
||||||
|
chmod(str(dirname), permission, recursive=True)
|
||||||
|
|
||||||
|
assert oct(os.stat(str(test_file)).st_mode & 0o777) == oct(permission)
|
||||||
|
assert oct(os.stat(dirname).st_mode & 0o777) == oct(permission)
|
||||||
|
|
||||||
|
|
||||||
|
def test_chmod_recursive(test_file):
|
||||||
|
dirname = os.path.dirname(str(test_file))
|
||||||
|
mkdir(os.path.join(dirname, "new_dir"))
|
||||||
|
permission = 0o721
|
||||||
|
fpermission = 0o720
|
||||||
|
chmod(str(dirname), permission, fmode=fpermission, recursive=True)
|
||||||
|
|
||||||
|
assert oct(os.stat(str(test_file)).st_mode & 0o777) == oct(fpermission)
|
||||||
|
assert oct(os.stat(dirname).st_mode & 0o777) == oct(permission)
|
||||||
|
|
||||||
|
|
||||||
|
def test_chmod_exception(test_file, mocker):
|
||||||
|
error = "foobar"
|
||||||
|
|
||||||
|
mocker.patch("os.chmod", side_effect=Exception(error))
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
chmod(str(test_file), 0o000)
|
||||||
|
|
||||||
|
translation = m18n.g(
|
||||||
|
"error_changing_file_permissions", path=test_file, error=str(error)
|
||||||
|
)
|
||||||
|
expected_msg = translation.format(path=test_file, error=str(error))
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
def test_remove_file(test_file):
|
def test_remove_file(test_file):
|
||||||
assert os.path.exists(str(test_file))
|
assert os.path.exists(str(test_file))
|
||||||
rm(str(test_file))
|
rm(str(test_file))
|
||||||
|
|
403
test/test_ldap.py
Normal file
403
test/test_ldap.py
Normal file
|
@ -0,0 +1,403 @@
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
|
||||||
|
from moulinette.authenticators import ldap as m_ldap
|
||||||
|
from moulinette import m18n
|
||||||
|
from moulinette.core import MoulinetteError
|
||||||
|
|
||||||
|
|
||||||
|
class TestLDAP:
|
||||||
|
def setup_method(self):
|
||||||
|
self.ldap_conf = {
|
||||||
|
"vendor": "ldap",
|
||||||
|
"name": "as-root",
|
||||||
|
"parameters": {"base_dn": "dc=yunohost,dc=org"},
|
||||||
|
"extra": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_authenticate_simple_bind_with_admin(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
self.ldap_conf["parameters"]["user_rdn"] = "cn=admin,dc=yunohost,dc=org"
|
||||||
|
ldap_interface = m_ldap.Authenticator(**self.ldap_conf)
|
||||||
|
ldap_interface.authenticate(password="yunohost")
|
||||||
|
|
||||||
|
assert ldap_interface.con
|
||||||
|
|
||||||
|
def test_authenticate_simple_bind_with_wrong_user(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
self.ldap_conf["parameters"]["user_rdn"] = "cn=yoloswag,dc=yunohost,dc=org"
|
||||||
|
ldap_interface = m_ldap.Authenticator(**self.ldap_conf)
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
ldap_interface.authenticate(password="yunohost")
|
||||||
|
|
||||||
|
translation = m18n.g("invalid_password")
|
||||||
|
expected_msg = translation.format()
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
assert ldap_interface.con is None
|
||||||
|
|
||||||
|
def test_authenticate_simple_bind_with_rdn_wrong_password(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
self.ldap_conf["parameters"]["user_rdn"] = "cn=admin,dc=yunohost,dc=org"
|
||||||
|
ldap_interface = m_ldap.Authenticator(**self.ldap_conf)
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
ldap_interface.authenticate(password="bad_password_lul")
|
||||||
|
|
||||||
|
translation = m18n.g("invalid_password")
|
||||||
|
expected_msg = translation.format()
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
assert ldap_interface.con is None
|
||||||
|
|
||||||
|
def test_authenticate_simple_bind_anonymous(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
self.ldap_conf["parameters"]["user_rdn"] = ""
|
||||||
|
ldap_interface = m_ldap.Authenticator(**self.ldap_conf)
|
||||||
|
ldap_interface.authenticate()
|
||||||
|
|
||||||
|
assert ldap_interface.con
|
||||||
|
|
||||||
|
def test_authenticate_sasl_non_interactive_bind(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
self.ldap_conf["parameters"]["user_rdn"] = (
|
||||||
|
"gidNumber=%s+uidNumber=%s,cn=peercred,cn=external,cn=auth"
|
||||||
|
% (os.getgid(), os.getuid())
|
||||||
|
)
|
||||||
|
ldap_interface = m_ldap.Authenticator(**self.ldap_conf)
|
||||||
|
|
||||||
|
assert ldap_interface.con
|
||||||
|
|
||||||
|
def test_authenticate_server_down(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
self.ldap_conf["parameters"]["user_rdn"] = "cn=admin,dc=yunohost,dc=org"
|
||||||
|
ldap_server.stop()
|
||||||
|
ldap_interface = m_ldap.Authenticator(**self.ldap_conf)
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
ldap_interface.authenticate(password="yunohost")
|
||||||
|
|
||||||
|
translation = m18n.g("ldap_server_down")
|
||||||
|
expected_msg = translation.format()
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
assert ldap_interface.con is None
|
||||||
|
|
||||||
|
def create_ldap_interface(self, user_rdn, password=None):
|
||||||
|
self.ldap_conf["parameters"]["user_rdn"] = user_rdn
|
||||||
|
ldap_interface = m_ldap.Authenticator(**self.ldap_conf)
|
||||||
|
if not ldap_interface.con:
|
||||||
|
ldap_interface.authenticate(password=password)
|
||||||
|
return ldap_interface
|
||||||
|
|
||||||
|
def test_admin_read(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"cn=admin,dc=yunohost,dc=org", "yunohost"
|
||||||
|
)
|
||||||
|
|
||||||
|
admin_info = ldap_interface.search("cn=admin,dc=yunohost,dc=org", attrs=None)[0]
|
||||||
|
assert "cn" in admin_info
|
||||||
|
assert admin_info["cn"] == ["admin"]
|
||||||
|
assert "description" in admin_info
|
||||||
|
assert admin_info["description"] == ["LDAP Administrator"]
|
||||||
|
assert "userPassword" in admin_info
|
||||||
|
assert admin_info["userPassword"][0].startswith("{CRYPT}$6$")
|
||||||
|
|
||||||
|
admin_info = ldap_interface.search(
|
||||||
|
"cn=admin,dc=yunohost,dc=org", attrs=["userPassword"]
|
||||||
|
)[0]
|
||||||
|
assert admin_info.keys() == ["userPassword"]
|
||||||
|
assert admin_info["userPassword"][0].startswith("{CRYPT}$6$")
|
||||||
|
|
||||||
|
def test_sasl_read(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"gidNumber=%s+uidNumber=%s,cn=peercred,cn=external,cn=auth"
|
||||||
|
% (os.getgid(), os.getuid())
|
||||||
|
)
|
||||||
|
|
||||||
|
admin_info = ldap_interface.search("cn=admin,dc=yunohost,dc=org", attrs=None)[0]
|
||||||
|
assert "cn" in admin_info
|
||||||
|
assert admin_info["cn"] == ["admin"]
|
||||||
|
assert "description" in admin_info
|
||||||
|
assert admin_info["description"] == ["LDAP Administrator"]
|
||||||
|
assert "userPassword" in admin_info
|
||||||
|
assert admin_info["userPassword"][0].startswith("{CRYPT}$6$")
|
||||||
|
|
||||||
|
admin_info = ldap_interface.search(
|
||||||
|
"cn=admin,dc=yunohost,dc=org", attrs=["userPassword"]
|
||||||
|
)[0]
|
||||||
|
assert admin_info.keys() == ["userPassword"]
|
||||||
|
assert admin_info["userPassword"][0].startswith("{CRYPT}$6$")
|
||||||
|
|
||||||
|
def test_anonymous_read(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface("")
|
||||||
|
|
||||||
|
admin_info = ldap_interface.search("cn=admin,dc=yunohost,dc=org", attrs=None)[0]
|
||||||
|
assert "cn" in admin_info
|
||||||
|
assert admin_info["cn"] == ["admin"]
|
||||||
|
assert "description" in admin_info
|
||||||
|
assert admin_info["description"] == ["LDAP Administrator"]
|
||||||
|
assert "userPassword" not in admin_info
|
||||||
|
|
||||||
|
admin_info = ldap_interface.search(
|
||||||
|
"cn=admin,dc=yunohost,dc=org", attrs=["userPassword"]
|
||||||
|
)[0]
|
||||||
|
assert not admin_info
|
||||||
|
|
||||||
|
def add_new_user(self, ldap_interface):
|
||||||
|
new_user = "new_user"
|
||||||
|
attr_dict = {
|
||||||
|
"objectClass": ["inetOrgPerson", "posixAccount"],
|
||||||
|
"sn": new_user,
|
||||||
|
"cn": new_user,
|
||||||
|
"userPassword": new_user,
|
||||||
|
"gidNumber": "666",
|
||||||
|
"uidNumber": "666",
|
||||||
|
"homeDirectory": "/home/" + new_user,
|
||||||
|
}
|
||||||
|
ldap_interface.add("uid=%s,ou=users" % new_user, attr_dict)
|
||||||
|
|
||||||
|
# Check if we can login as the new user
|
||||||
|
assert self.create_ldap_interface(
|
||||||
|
"uid=%s,ou=users,dc=yunohost,dc=org" % new_user, new_user
|
||||||
|
).con
|
||||||
|
|
||||||
|
return ldap_interface.search(
|
||||||
|
"uid=%s,ou=users,dc=yunohost,dc=org" % new_user, attrs=None
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
def test_admin_add(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"cn=admin,dc=yunohost,dc=org", "yunohost"
|
||||||
|
)
|
||||||
|
|
||||||
|
new_user_info = self.add_new_user(ldap_interface)
|
||||||
|
assert "cn" in new_user_info
|
||||||
|
assert new_user_info["cn"] == ["new_user"]
|
||||||
|
assert "sn" in new_user_info
|
||||||
|
assert new_user_info["sn"] == ["new_user"]
|
||||||
|
assert "uid" in new_user_info
|
||||||
|
assert new_user_info["uid"] == ["new_user"]
|
||||||
|
assert "objectClass" in new_user_info
|
||||||
|
assert "inetOrgPerson" in new_user_info["objectClass"]
|
||||||
|
assert "posixAccount" in new_user_info["objectClass"]
|
||||||
|
|
||||||
|
def test_sasl_add(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"gidNumber=%s+uidNumber=%s,cn=peercred,cn=external,cn=auth"
|
||||||
|
% (os.getgid(), os.getuid())
|
||||||
|
)
|
||||||
|
|
||||||
|
new_user_info = self.add_new_user(ldap_interface)
|
||||||
|
assert "cn" in new_user_info
|
||||||
|
assert new_user_info["cn"] == ["new_user"]
|
||||||
|
assert "sn" in new_user_info
|
||||||
|
assert new_user_info["sn"] == ["new_user"]
|
||||||
|
assert "uid" in new_user_info
|
||||||
|
assert new_user_info["uid"] == ["new_user"]
|
||||||
|
assert "objectClass" in new_user_info
|
||||||
|
assert "inetOrgPerson" in new_user_info["objectClass"]
|
||||||
|
assert "posixAccount" in new_user_info["objectClass"]
|
||||||
|
|
||||||
|
def test_anonymous_add(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface("")
|
||||||
|
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
self.add_new_user(ldap_interface)
|
||||||
|
|
||||||
|
translation = m18n.g("ldap_operation_error", action="add")
|
||||||
|
expected_msg = translation.format(action="add")
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
def remove_new_user(self, ldap_interface):
|
||||||
|
new_user_info = self.add_new_user(
|
||||||
|
self.create_ldap_interface(
|
||||||
|
"gidNumber=%s+uidNumber=%s,cn=peercred,cn=external,cn=auth"
|
||||||
|
% (os.getgid(), os.getuid()),
|
||||||
|
"yunohost",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
uid = new_user_info["uid"][0]
|
||||||
|
ldap_interface.remove("uid=%s,ou=users" % uid)
|
||||||
|
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
ldap_interface.search(
|
||||||
|
"uid=%s,ou=users,dc=yunohost,dc=org" % uid, attrs=None
|
||||||
|
)
|
||||||
|
|
||||||
|
translation = m18n.g("ldap_operation_error", action="search")
|
||||||
|
expected_msg = translation.format(action="search")
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
def test_admin_remove(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"cn=admin,dc=yunohost,dc=org", "yunohost"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.remove_new_user(ldap_interface)
|
||||||
|
|
||||||
|
def test_sasl_remove(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"gidNumber=%s+uidNumber=%s,cn=peercred,cn=external,cn=auth"
|
||||||
|
% (os.getgid(), os.getuid())
|
||||||
|
)
|
||||||
|
|
||||||
|
self.remove_new_user(ldap_interface)
|
||||||
|
|
||||||
|
def test_anonymous_remove(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface("")
|
||||||
|
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
self.remove_new_user(ldap_interface)
|
||||||
|
|
||||||
|
translation = m18n.g("ldap_operation_error", action="remove")
|
||||||
|
expected_msg = translation.format(action="remove")
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
def update_new_user(self, ldap_interface, new_rdn=False):
|
||||||
|
new_user_info = self.add_new_user(
|
||||||
|
self.create_ldap_interface(
|
||||||
|
"gidNumber=%s+uidNumber=%s,cn=peercred,cn=external,cn=auth"
|
||||||
|
% (os.getgid(), os.getuid()),
|
||||||
|
"yunohost",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
uid = new_user_info["uid"][0]
|
||||||
|
new_user_info["uidNumber"] = ["555"]
|
||||||
|
new_user_info["gidNumber"] = ["555"]
|
||||||
|
new_another_user_uid = "new_another_user"
|
||||||
|
if new_rdn:
|
||||||
|
new_rdn = "uid=%s" % new_another_user_uid
|
||||||
|
ldap_interface.update("uid=%s,ou=users" % uid, new_user_info, new_rdn)
|
||||||
|
|
||||||
|
if new_rdn:
|
||||||
|
uid = new_another_user_uid
|
||||||
|
return ldap_interface.search(
|
||||||
|
"uid=%s,ou=users,dc=yunohost,dc=org" % uid, attrs=None
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
def test_admin_update(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"cn=admin,dc=yunohost,dc=org", "yunohost"
|
||||||
|
)
|
||||||
|
|
||||||
|
new_user_info = self.update_new_user(ldap_interface)
|
||||||
|
assert new_user_info["uid"] == ["new_user"]
|
||||||
|
assert new_user_info["uidNumber"] == ["555"]
|
||||||
|
assert new_user_info["gidNumber"] == ["555"]
|
||||||
|
|
||||||
|
def test_admin_update_new_rdn(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"cn=admin,dc=yunohost,dc=org", "yunohost"
|
||||||
|
)
|
||||||
|
|
||||||
|
new_user_info = self.update_new_user(ldap_interface, True)
|
||||||
|
assert new_user_info["uid"] == ["new_another_user"]
|
||||||
|
assert new_user_info["uidNumber"] == ["555"]
|
||||||
|
assert new_user_info["gidNumber"] == ["555"]
|
||||||
|
|
||||||
|
def test_sasl_update(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"gidNumber=%s+uidNumber=%s,cn=peercred,cn=external,cn=auth"
|
||||||
|
% (os.getgid(), os.getuid())
|
||||||
|
)
|
||||||
|
|
||||||
|
new_user_info = self.update_new_user(ldap_interface)
|
||||||
|
assert new_user_info["uid"] == ["new_user"]
|
||||||
|
assert new_user_info["uidNumber"] == ["555"]
|
||||||
|
assert new_user_info["gidNumber"] == ["555"]
|
||||||
|
|
||||||
|
def test_sasl_update_new_rdn(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"cn=admin,dc=yunohost,dc=org", "yunohost"
|
||||||
|
)
|
||||||
|
|
||||||
|
new_user_info = self.update_new_user(ldap_interface, True)
|
||||||
|
assert new_user_info["uid"] == ["new_another_user"]
|
||||||
|
assert new_user_info["uidNumber"] == ["555"]
|
||||||
|
assert new_user_info["gidNumber"] == ["555"]
|
||||||
|
|
||||||
|
def test_anonymous_update(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface("")
|
||||||
|
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
self.update_new_user(ldap_interface)
|
||||||
|
|
||||||
|
translation = m18n.g("ldap_operation_error", action="update")
|
||||||
|
expected_msg = translation.format(action="update")
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
def test_anonymous_update_new_rdn(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface("")
|
||||||
|
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
self.update_new_user(ldap_interface, True)
|
||||||
|
|
||||||
|
translation = m18n.g("ldap_operation_error", action="update")
|
||||||
|
expected_msg = translation.format(action="update")
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
def test_empty_update(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"cn=admin,dc=yunohost,dc=org", "yunohost"
|
||||||
|
)
|
||||||
|
|
||||||
|
new_user_info = self.update_new_user(ldap_interface)
|
||||||
|
assert new_user_info["uid"] == ["new_user"]
|
||||||
|
assert new_user_info["uidNumber"] == ["555"]
|
||||||
|
assert new_user_info["gidNumber"] == ["555"]
|
||||||
|
|
||||||
|
uid = new_user_info["uid"][0]
|
||||||
|
|
||||||
|
assert ldap_interface.update("uid=%s,ou=users" % uid, new_user_info)
|
||||||
|
|
||||||
|
def test_get_conflict(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"cn=admin,dc=yunohost,dc=org", "yunohost"
|
||||||
|
)
|
||||||
|
self.add_new_user(ldap_interface)
|
||||||
|
|
||||||
|
conflict = ldap_interface.get_conflict({"uid": "new_user"})
|
||||||
|
assert conflict == ("uid", "new_user")
|
||||||
|
|
||||||
|
conflict = ldap_interface.get_conflict(
|
||||||
|
{"uid": "new_user"}, base_dn="ou=users,dc=yunohost,dc=org"
|
||||||
|
)
|
||||||
|
assert conflict == ("uid", "new_user")
|
||||||
|
|
||||||
|
conflict = ldap_interface.get_conflict({"uid": "not_a_user"})
|
||||||
|
assert not conflict
|
||||||
|
|
||||||
|
def test_validate_uniqueness(self, ldap_server):
|
||||||
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
ldap_interface = self.create_ldap_interface(
|
||||||
|
"cn=admin,dc=yunohost,dc=org", "yunohost"
|
||||||
|
)
|
||||||
|
self.add_new_user(ldap_interface)
|
||||||
|
|
||||||
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
|
ldap_interface.validate_uniqueness({"uid": "new_user"})
|
||||||
|
|
||||||
|
translation = m18n.g(
|
||||||
|
"ldap_attribute_already_exists", attribute="uid", value="new_user"
|
||||||
|
)
|
||||||
|
expected_msg = translation.format(attribute="uid", value="new_user")
|
||||||
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
assert ldap_interface.validate_uniqueness({"uid": "not_a_user"})
|
|
@ -33,9 +33,17 @@ def test_download_ssl_error(test_url):
|
||||||
download_text(test_url)
|
download_text(test_url)
|
||||||
|
|
||||||
|
|
||||||
|
def test_download_connection_error(test_url):
|
||||||
|
with requests_mock.Mocker() as mock:
|
||||||
|
exception = requests.exceptions.ConnectionError
|
||||||
|
mock.register_uri("GET", test_url, exc=exception)
|
||||||
|
with pytest.raises(MoulinetteError):
|
||||||
|
download_text(test_url)
|
||||||
|
|
||||||
|
|
||||||
def test_download_timeout(test_url):
|
def test_download_timeout(test_url):
|
||||||
with requests_mock.Mocker() as mock:
|
with requests_mock.Mocker() as mock:
|
||||||
exception = requests.exceptions.ConnectTimeout
|
exception = requests.exceptions.Timeout
|
||||||
mock.register_uri("GET", test_url, exc=exception)
|
mock.register_uri("GET", test_url, exc=exception)
|
||||||
with pytest.raises(MoulinetteError):
|
with pytest.raises(MoulinetteError):
|
||||||
download_text(test_url)
|
download_text(test_url)
|
||||||
|
|
|
@ -4,6 +4,8 @@ from subprocess import CalledProcessError
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from moulinette.utils.process import run_commands
|
from moulinette.utils.process import run_commands
|
||||||
|
from moulinette.utils.process import call_async_output
|
||||||
|
from moulinette.utils.process import check_output
|
||||||
|
|
||||||
|
|
||||||
def test_run_shell_command_list(test_file):
|
def test_run_shell_command_list(test_file):
|
||||||
|
@ -15,3 +17,104 @@ def test_run_shell_command_list(test_file):
|
||||||
def test_run_shell_bad_cmd():
|
def test_run_shell_bad_cmd():
|
||||||
with pytest.raises(CalledProcessError):
|
with pytest.raises(CalledProcessError):
|
||||||
run_commands(["yolo swag"])
|
run_commands(["yolo swag"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_shell_bad_cmd_with_callback():
|
||||||
|
def callback(a, b, c):
|
||||||
|
assert isinstance(a, int)
|
||||||
|
assert isinstance(b, str)
|
||||||
|
assert isinstance(c, str)
|
||||||
|
return True
|
||||||
|
|
||||||
|
assert run_commands(["yolo swag", "yolo swag", "yolo swag"], callback=callback) == 3
|
||||||
|
|
||||||
|
def callback(a, b, c):
|
||||||
|
assert isinstance(a, int)
|
||||||
|
assert isinstance(b, str)
|
||||||
|
assert isinstance(c, str)
|
||||||
|
return False
|
||||||
|
|
||||||
|
assert run_commands(["yolo swag", "yolo swag"], callback=callback) == 1
|
||||||
|
|
||||||
|
def callback(a, b, c):
|
||||||
|
assert isinstance(a, int)
|
||||||
|
assert isinstance(b, str)
|
||||||
|
assert isinstance(c, tuple)
|
||||||
|
return True
|
||||||
|
|
||||||
|
run_commands(["yolo swag"], separate_stderr=True, callback=callback)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_shell_bad_callback():
|
||||||
|
callback = 1
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
run_commands(["ls"], callback=callback)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_shell_kwargs():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
run_commands([""], stdout="None")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
run_commands([""], stderr="None")
|
||||||
|
|
||||||
|
run_commands(["ls"], cwd="/tmp")
|
||||||
|
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
run_commands(["ls"], cwd="/yoloswag")
|
||||||
|
|
||||||
|
|
||||||
|
def test_call_async_output(test_file):
|
||||||
|
def callback(a):
|
||||||
|
assert a == "foo\n" or a == "bar\n"
|
||||||
|
|
||||||
|
call_async_output(["cat", str(test_file)], callback)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
call_async_output(["cat", str(test_file)], 1)
|
||||||
|
|
||||||
|
def callbackA(a):
|
||||||
|
assert a == "foo\n" or a == "bar\n"
|
||||||
|
|
||||||
|
def callbackB(a):
|
||||||
|
pass
|
||||||
|
|
||||||
|
callback = (callbackA, callbackB)
|
||||||
|
call_async_output(["cat", str(test_file)], callback)
|
||||||
|
|
||||||
|
|
||||||
|
def test_call_async_output_kwargs(test_file, mocker):
|
||||||
|
def callback(a):
|
||||||
|
assert a == "foo\n" or a == "bar\n"
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
call_async_output(["cat", str(test_file)], callback, stdout=None)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
call_async_output(["cat", str(test_file)], callback, stderr=None)
|
||||||
|
|
||||||
|
call_async_output(["cat", str(test_file)], callback, stdinfo=None)
|
||||||
|
|
||||||
|
def callbackA(a):
|
||||||
|
assert a == "foo\n" or a == "bar\n"
|
||||||
|
|
||||||
|
def callbackB(a):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def callbackC(a):
|
||||||
|
pass
|
||||||
|
|
||||||
|
callback = (callbackA, callbackB, callbackC)
|
||||||
|
|
||||||
|
dirname = os.path.dirname(str(test_file))
|
||||||
|
os.mkdir(os.path.join(dirname, "teststdinfo"))
|
||||||
|
call_async_output(
|
||||||
|
["cat", str(test_file)],
|
||||||
|
callback,
|
||||||
|
stdinfo=os.path.join(dirname, "teststdinfo", "teststdinfo"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_output(test_file):
|
||||||
|
assert check_output(["cat", str(test_file)], shell=False) == "foo\nbar\n"
|
||||||
|
|
||||||
|
assert check_output("cat %s" % str(test_file)) == "foo\nbar\n"
|
||||||
|
|
|
@ -4,6 +4,7 @@ from moulinette.utils.text import search, searchf, prependlines, random_ascii
|
||||||
def test_search():
|
def test_search():
|
||||||
assert search("a", "a a a") == ["a", "a", "a"]
|
assert search("a", "a a a") == ["a", "a", "a"]
|
||||||
assert search("a", "a a a", count=2) == ["a", "a"]
|
assert search("a", "a a a", count=2) == ["a", "a"]
|
||||||
|
assert search("a", "a a a", count=-1) == "a"
|
||||||
assert not search("a", "c c d")
|
assert not search("a", "c c d")
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue