Add API feature

Some major changes have been made to handle this new feature :
- LSsession now have a flag about API mode. The displayTemplate() and
  displayAjaxReturn() methods have been adjust to correctly handle this
  mode.
- LSauth system have been adjust to handle a custom API mode :
  - LSauthMethod can support or not this mode : the $api_mode_supported
    permit to defined if supported (default, false). Currently, only
    HTTP (default in API mode) and annonymous mode support it.
  - An api_access parameter permit to configure witch type of user
    LSobject types could use the API. This flag must be set to True to
    allow a type of LSobject (default: False). In a same way, a
    web_access parameter now permit to disable Web access for some
    types of users (but this parameter is optional and its default value
    is True).
  - The HTTP method is the privileged first method for API mode. In this
    mode, if auth data aren't present in environment, it will request it
    by triggered a 403 HTTP error. Realm can be configured with new
    LSAUTHMETHOD_HTTP_API_REALM constant.
- The LStemplate system handle API mode to correctly react on errors: it
  return a JSON answer instead of HTML page. Error pages also now return
  adjusted HTTP code (404 or 500).
- The LSurl system have been adjust to handle API mode :
  - On declaring handlers, we could now specify if it's an API view with
    new $api_mode paremeter of add_handler() method
  - The LSurlRequest object have a new attribute to check if it's an API
    request
  - The error_404() method handle the API mode to return JSON answer.
    Furthermore, if no handlers matched with the requested URL, API mode
    is automatically enabled if the requested URL starts with 'api/'.
- LSform implement it own API mode flag and a new submited flag that
  be toggle via the new setSubmited() method. Some major changes also
  occured on LSformElement classes to specifically handle API
  input/output for each types of attributes:
  - a new getApiValue() method permit to retrieve the API value of the
    attribute (on show API view)
  - the getPostData() method now have to correctly handle API input for
    the attribute (on create/modify API views). A programmatic way have
    been adopted for each types of attributes.
- The LSimport and LScli create/modify commands also evolved to enable
  API mode of the LSform. This permit to take advantage of the new
  capability of LSform/LSformElement to handle input values with a
  programmatic way.
- New routes have been add to handle API views. All this new routes
  start with 'api/1.0/' and use the same URL schema as the web UI. The
  API currently permit to search/show/add/modify/remove LSobjects and
  manages their relations.
This commit is contained in:
Benjamin Renard 2021-02-03 14:40:28 +01:00
parent e92bc10c8b
commit 0ec390e1fe
36 changed files with 2329 additions and 717 deletions

View file

@ -22,6 +22,7 @@ book SYSTEM "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
<!ENTITY install SYSTEM "install/install.docbook">
<!ENTITY install-arbo SYSTEM "install/arbo.docbook">
<!ENTITY upgrade SYSTEM "upgrade/upgrade.docbook">
<!ENTITY api SYSTEM "api/api.docbook">
<!ENTITY contrib SYSTEM "contrib/contrib.docbook">
]>
@ -53,5 +54,7 @@ book SYSTEM "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
&conf;
&api;
&contrib;
</book>

435
doc/api/api.docbook Normal file
View file

@ -0,0 +1,435 @@
<?xml version="1.0" encoding="UTF-8" ?>
<chapter>
<title>API</title>
<para>Depuis la version 4.0, LdapSaisie offre une API visant à permettre de faire
les mêmes choses que ce qu'il est possible d'accomplir via l'interface web. L'idée
n'est bien entendue pas de se substituer systématiquement à la possibilité de se
connecter directement à l'annuaire, mais plutôt d'offrir une API web pour l'intégration
d'outil préférant ce mode d'interaction, ou encore, pour exposer des méthodes accès aux
données de l'annuaire tout en profitant des logiques métiers implémentées/configurées
dans LdapSaisie : validation syntaxique et d'unicité, règle de génération et
d'interdépendances des attributs, déclencheurs, ...</para>
<note><simpara>Cette API est actuellement dans une phase de test et n'offre pas encore
toutes les fonctionnalités proposées dans l'interface web. Elle est vouée à évoluer pour
intégrer petit à petit un maximum de fonctionnalités. Des contributions à ce sujet seront
plus qu'appréciée !</simpara></note>
<sect1>
<title>Authentification</title>
<para>L'authentification à l'API utilise le même composant <literal>LSauth</literal> que
lors d'une authentification à l'interface web, cependant, ce composant s'adapte pour
prendre en compte de mode de connexion. Par défaut, la méthode d'authentification utilisée
sera &LSauthMethod_HTTP; et permettra de se connecter en spécifiant le nom d'utilisateur
et le mot de l'utilisateur cherchant à se connecter via une authentification basique HTTP.
</para>
<warning><simpara>Il est à noter que tous les types d'utilisateur ne peuvent pas forcément
utiliser l'API : le paramètre <literal>api_access</literal> doit être explicitement
positionné à <literal>True</literal> dans <link linkend='config-srv-ldap'>la configuration
du serveur LDAP</link>.</simpara></warning>
<para>Une fois connecté, l'utilisateur endossera les droits associés à ses &LSprofiles;,
tout comme un utilisateur connecté à l'interface web.</para>
</sect1>
<sect1>
<title>Méthodes exposées</title>
<para>Les URLs des méthodes de l'API ont été construites par mimétisme sur celle de l'interface
web et sous la racine web <literal>api/</literal>. Par ailleurs, un numéro de version d'API a
été insérée dans chacune d'elles afin d'anticiper toutes évolutions futures majeures nécéssitants
de conserver une rétrocompatibilité avec les anciennes versions de l'API.</para>
<para>Toutes les méthodes retournent des informations au format JSON et accepte le paramètre
<literal>pretty</literal> permettant d'obtenir un retour plus facilement lisible. Les chaines de
caractères échangées doivent par ailleurs être encodées en UTF-8. On trouvera par ailleurs dans
le retour JSON :
<variablelist>
<varlistentry>
<term>success</term>
<listitem><simpara>Booléen précisant si l'action demandée a correctement été exécutée.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>messages</term>
<listitem><simpara>Ce tableau pourra être présent et lister les messages d'informations générées
par l'action demandée. Il s'agira des mêmes messages que ceux affichés dans l'interface web
lorsque les actions équivalentes y sont faites.</simpara></listitem>
</varlistentry>
<varlistentry>
<term>errors</term>
<listitem><simpara>Ce tableau pourra être présent et lister les messages d'erreurs générées
par l'action demandée.</simpara></listitem>
</varlistentry>
</variablelist>
</para>
<note><simpara>Les messages d'informations et d'erreurs générées par l'application sont traduites dans
la langue courante qui peut être spécifiée via le paramètre <literal>lang</literal> accepté par toutes
les méthodes (exemple : <literal>fr_FR</literal> ou <literal>en_US</literal>).</simpara></note>
<para>Lorsqu'une méthode cible un type d'objets, voir un objet en particulier, ces informations seront
transmises dans l'URL appelée. Si le type d'objet ou l'objet demandé est introuvable, une erreur HTTP
404 sera générée.</para>
<!-- Début Liste des méthodes exposées -->
<variablelist>
<title>Liste des méthodes exposées</title>
<varlistentry>
<term>/api/1.0/object/[object type]</term>
<listitem>
<para>Cette méthode permet de rechercher/lister les informations d'un type d'objets de
l'annuaire en particulier. Le type de l'objet est précisé dans l'URL et doit être encodé en
conséquence. Par mimétisme du comportement de l'interface web, la recherche est paginée et
accepte des paramètres similaires en plus de paramètre plus appropriés à un fonctionnement
programmatique :
<variablelist>
<title>Paramètres acceptés</title>
<varlistentry>
<term>filter</term>
<listitem><simpara>Permet de spécifier un filtre de recherche LDAP personnalisé. Celui-ci
sera combiné avec les paramètres propres au type d'objets recherchés et aux autres
paramètres spécifiés (<literal>pattern</literal> par exemple).</simpara>
<warning><simpara>Pour le moment, seuls les filtres simples du type <literal>attribut=valeur
</literal> sont acceptés ici.</simpara></warning>
</listitem>
</varlistentry>
<varlistentry>
<term>predefinedFilter</term>
<listitem><simpara>Permet de spécifier un des filtres de recherche LDAP prédéfinis dans la
configuration du type d'objet.</simpara></listitem>
</varlistentry>
<varlistentry>
<term>pattern</term>
<listitem><simpara>Permet de spécifier un mot clé de recherche, comme proposé dans
l'interface web.</simpara></listitem>
</varlistentry>
<varlistentry>
<term>approx</term>
<listitem><simpara>Booléen permettant d'activer/désactiver la recherche approximative
sur le mot clé. Les valeurs acceptées sont <literal>1</literal> ou <literal>0</literal>.
</simpara></listitem>
</varlistentry>
<varlistentry>
<term>basedn</term>
<listitem><simpara>Permet de spécifier une base de recherche personnalisé pour la recherche.
</simpara></listitem>
</varlistentry>
<varlistentry>
<term>subDn</term>
<listitem><simpara>Dans le cas d'un serveur LDAP configuré avec des <link linkend="config-subDn">
sous-niveaux de connexion</link>, permet de spécifier le sous-niveau pour la recherche.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>scope</term>
<listitem><simpara>Permet de spécifier l'étendue de la recherche dans l'annuaire. Valeurs acceptées:
<literal>sub</literal>, <literal>one</literal> et <literal>base</literal>.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>recursive</term>
<listitem><simpara>Booléen permettant d'activer/désactiver la recherche recursive, c'est à dire une
recherche à la racine de l'annuaire (ou du <link linkend="config-subDn">sous-niveau de connexion</link>)
avec une étendue de recherche maximale. Les valeurs acceptées sont <literal>1</literal> ou
<literal>0</literal>.</simpara></listitem>
</varlistentry>
<varlistentry>
<term>displayFormat</term>
<listitem><simpara>Permet de spécifier un &LSformat; personnalisé pour le nom des objets dans le résultat
de recherche.</simpara></listitem>
</varlistentry>
<varlistentry>
<term>extraDisplayedColumns</term>
<listitem><simpara>Booléen permettant d'activer le retour des colonnes personnalisées dans le résultat
de recherche. Les valeurs acceptées sont <literal>1</literal> ou <literal>0</literal>.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>attributes</term>
<listitem><simpara>Liste des attributs supplémentaires que devra retourner la recherche.</simpara>
<warning><simpara>Attention, ici ce sont les valeurs brutes des attributs qui seront retournées et
par forcément les valeurs telle que retournées habituellement.</simpara></warning>
</listitem>
</varlistentry>
<varlistentry>
<term>sortBy</term>
<listitem><simpara>Permet de préciser sur quelle information le résultat de recherche doit être
trié. Valeurs acceptées : <literal>displayName</literal>, <literal>subDn</literal> ou un des noms
des colonnes personnalisées.</simpara></listitem>
</varlistentry>
<varlistentry>
<term>sortDirection</term>
<listitem><simpara>Permet de préciser l'ordre de tri du résultat de recherche. Valeurs acceptées :
<literal>ASC</literal> (A-Z) ou <literal>DESC</literal> (Z-A).</simpara></listitem>
</varlistentry>
<varlistentry>
<term>page</term>
<listitem><simpara>Permet de préciser la page du résultat de recherche.</simpara></listitem>
</varlistentry>
<varlistentry>
<term>nbObjectsByPage</term>
<listitem><simpara>Permet de préciser le nombre maximum d'objets retournés par page du résultat de
recherche.</simpara></listitem>
</varlistentry>
<varlistentry>
<term>all</term>
<listitem><simpara>Permet de réclamer le résultat complet de la recherche (désactivation
de la pagination). Seul la présence de ce paramètre suffit à activer ce comportement, sa
valeur n'a pas d'importance.</simpara></listitem>
</varlistentry>
<varlistentry>
<term>withoutCache</term>
<listitem><simpara>Booléen permettant de désactiver l'utilisation du cache. Les valeurs acceptées
sont <literal>1</literal> ou <literal>0</literal>.</simpara></listitem>
</varlistentry>
</variablelist>
</para>
<programlisting linenumbering="unnumbered">
<citetitle>Exemple</citetitle>
<![CDATA[# curl -u username:secret 'https://ldapsaisie/api/1.0/object/LSpeople?extraDisplayedColumns=1&pretty'
{
"success": true,
"objects": {
"uid=hmartin,ou=people,o=ls": {
"name": "Henri MARTIN",
"Mail": "henri.martin@ls.com"
},
"uid=s.ldapsaisie,ou=people,o=ls": {
"name": "Secretariat LdapSaisie",
"Mail": "secretariat@ldapsaisie.biz"
},
"uid=ls,ou=people,o=ls": {
"name": "LdapSaisie",
"Mail": "ldap.saisie@ls.com"
},
"uid=erwpa,ou=people,o=ls": {
"name": "Erwan PAGE",
"Mail": "erwan.page@ldapsaisie.biz"
},
"uid=user2,ou=people,ou=company1,ou=companies,o=ls": {
"name": "prenom2 nom2",
"Mail": "user2@ls.com"
}
},
"total": 14,
"page": 1,
"nbPages": 3
}]]>
</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term>/api/1.0/object/[object type]/[dn]</term>
<listitem>
<para>Cette méthode permet de récupérer les informations d'un objet de l'annuaire au format
JSON. Le type de l'objet et son DN sont précisés dans l'URL et doivent être encodés en
conséquence.
<programlisting linenumbering="unnumbered">
<citetitle>Exemple</citetitle>
<![CDATA[# curl -u username:secret 'https://ldapsaisie/api/1.0/object/LSpeople/uid=hmartin,ou=people,o=ls?pretty'
{
"dn": "uid=hmartin,ou=people,o=ls",
"type": "LSpeople",
"name": "Henri MARTIN",
"attributes": {
"uid": "hmartin",
"givenName": "Henri",
"sn": "MARTIN",
"cn": "Henri MARTIN",
"mail": "henri.martin@ls.com",
"personalTitle": "M.",
"description": [],
"jpegPhoto": null,
"lsGodfatherDn": {
"uid=eeggs,ou=people,o=ls": {
"name": "Easter Eggs",
"object_type": "LSpeople"
}
},
"uidNumber": "101022",
"gidNumber": "102001",
"loginShell": "no",
"homeDirectory": "\/home\/com",
"gecos": null,
"shadowExpire": null,
"shadowMax": null,
"shadowInactive": null,
"shadowLastChange": null,
"sambaSID": "S-1-5-21-2421470416-3566881284-3047381809-203044",
"sambaPrimaryGroupSID": "S-1-5-21-2421470416-3566881284-3047381809-205003",
"sambaAcctFlags": [
"U"
],
"sambaHomeDrive": null,
"sambaHomePath": null,
"sambaProfilePath": null,
"sambaLogonScript": null,
"sambaLogonTime": null,
"sambaLogoffTime": null,
"sambaKickoffTime": null,
"sambaPwdLastSet": null,
"sambaPwdMustChange": null,
"sambaPwdCanChange": null
},
"relations": {
"groups": {
"cn=direction,ou=groups,o=ls": "direction",
"cn=secretariat,ou=groups,o=ls": "secretariat"
},
"godfather": []
}
}]]>
</programlisting>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>/api/1.0/object/[object type]/create</term>
<listitem>
<para>Cette méthode permet de créer un objet dans l'annuaire. Le type de l'objet qui sera créé est
précisé dans l'URL et doit être encodé en conséquence. Les informations de l'objet doivent est
transmises au format <literal>x-www-form-urlencoded</literal>. Elles peuvent également être au
format <literal>multipart/form-data</literal>, en particulier si votre requête contient une image.
Par mimétisme avec l'interface web, seuls les attributs prévus dans le formulaire de création du
type d'objet peuvent être passées ici. De la même manière, les attributs non-spécifiés ici, pouront
être auto-générés en accord avec leur configuration et la requête sera acceptée uniquement si tous les
attributs obligatoires y sont spécifiés ou s'ils peuvent être auto-générés.</para>
<para>Le format et la syntaxe des valeurs des attributs dépends de leur type HTML. Ainsi, par exemple,
un attribut de type HTML <literal>boolean</literal> acceptera comme valeurs possibles <literal>yes
</literal> ou <literal>no</literal>. Pour plus de détails sur le type de valeur acceptée par un type
d'attribut HTML en particulier, consultez sa documentation. Vous pouvez également analyser le code de
la méthode <literal>getPostData()</literal> de la classe PHP correspondante.</para>
<para>Si l'application détecte un souci avec les informations transmises pour les attributs, un tableau
<literal>fields_errors</literal> sera présent dans la réponse JSON et contiendra pour chacun des attributs
problématique, un tableau des messages d'erreurs générées par l'application.</para>
<para>Si le type d'objet en prévoit, vous pouvez également utiliser un
<link linkend="config-LSobject-LSform-dataEntryForm">masque de saisie</link> via le paramètre <literal>
dataEntryForm</literal>.</para>
<programlisting linenumbering="unnumbered">
<citetitle>Exemple</citetitle>
<![CDATA[# curl -u username:secret 'https://ldapsaisie/api/1.0/object/LSpeople/create?pretty' -d "uid=foo.bar&personalTitle=M.&givenName=foo&sn=bar&cn=Foo Bar&mail=foo.bar@example.com&userPassword=Y0urS3cr3t&lsGodfatherDn[]=uid=admin,ou=people,o=ls&gidNumber=70000"
{
"success": true,
"type": "LSpeople",
"dn": "uid=foo.bar,ou=people,o=ls",
"name": "Foo Bar",
"messages": [
"Le mail de notification a \u00e9t\u00e9 envoy\u00e9.",
"L'objet a \u00e9t\u00e9 ajout\u00e9."
]
}]]>
</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term>/api/1.0/object/[object type]/[dn]/modify</term>
<listitem>
<para>Cette méthode permet de modifier un objet dans l'annuaire. Le type de l'objet et son DN sont
précisés dans l'URL et doivent être encodés en conséquence. Les informations de l'objet à modifier
doivent être transmises au même format que pour la méthode <literal>create</literal> (voir ci-dessus).
Comme pour cette dernière, seuls les attributs prévus dans le formulaire de modification du type
d'objet peuvent être passées ici et la réponse JSON pourra contenir un tableau <literal>fields_errors
</literal> contenant les erreurs générées par l'application au sujet des valeurs transmises pour les
attributs.</para>
<programlisting linenumbering="unnumbered">
<citetitle>Exemple</citetitle>
<![CDATA[# curl -u username:secret 'https://ldapsaisie/api/1.0/object/LSpeople/uid=foo.bar,ou=peopleo=ls/modify?pretty' -d "givenName=foo&sn=bar&cn=Foo Bar"
{
"dn": "uid=foo.bar,ou=people,o=ls",
"type": "LSpeople",
"name": "Foo Bar",
"success": true,
"messages": [
"L'objet a bien \u00e9t\u00e9 modifi\u00e9."
]
}]]>
</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term>/api/1.0/object/[object type]/[dn]/remove</term>
<listitem>
<para>Cette méthode permet de supprimer un objet dans l'annuaire. Le type de l'objet et son DN sont
précisés dans l'URL et doivent être encodés en conséquence.</para>
<programlisting linenumbering="unnumbered">
<citetitle>Exemple</citetitle>
<![CDATA[# curl -u username:secret 'https://ldapsaisie/api/1.0/object/LSpeople/uid=foo.bar,ou=people,o=ls/remove?pretty'
{
"dn": "uid=foo.bar,ou=people,o=ls",
"type": "LSpeople",
"name": "Foo Bar",
"success": true,
"messages": [
"Foo Bar a bien \u00e9t\u00e9 supprim\u00e9."
]
}]]>
</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term>/api/1.0/object/[object type]/[dn]/relation/[relation]</term>
<listitem>
<para>Cette méthode permet de gérer les objets en relation avec un objet en particulier de
l'annuaire. Le type de l'objet, son DN et le nom de la relation sont précisés dans l'URL et
doivent être encodés en conséquence. Cette méthode accepte les paramètres <literal>add
</literal> et <literal>remove</literal> permettant de lister le ou les DN d'objet(s) à
respectivement ajouter ou supprimer parmis les objets actuellement en relation avec l'objet
spécifié. Si aucun DN n'est spécifié comme devant être ajouté ou supprimé, la méthode
retournera simplement les DN des objets en relation. En cas de modification demandée, la
méthode retournera la nouvelle liste des DNs des objets en relation, quel que soit le résultat
de l'opération de mise à jour.</para>
<programlisting linenumbering="unnumbered">
<citetitle>Exemple</citetitle>
<![CDATA[# curl -u username:secret 'https://ldapsaisie/api/1.0/object/LSpeople/uid=foo.bar,ou=people,o=ls/relation/groups?pretty&add[]=cn=test34,ou=groups,o=ls&add[]=cn=testbr,ou=groups,o=ls'
{
"dn": "uid=foo.bar,ou=people,o=ls",
"type": "LSpeople",
"name": "Foo Bar",
"relation": "groups",
"success": true,
"relatedObjects": [
"cn=test34,ou=groups,o=ls",
"cn=testbr,ou=groups,o=ls"
],
"messages": [
"Objects in relation updated."
]
}]]>
</programlisting>
</listitem>
</varlistentry>
</variablelist>
</sect1>
</chapter>

View file

@ -3,6 +3,4 @@
<!ENTITY conf-LSauthMethod_CAS SYSTEM "LSauthMethod_CAS.docbook">
<!ENTITY conf-LSauthMethod_anonymous SYSTEM "LSauthMethod_anonymous.docbook">
<!ENTITY LSauthMethod_HTTP "<link linkend='config-LSauthMethod_HTTP'>LSauthMethod_HTTP</link>">

View file

@ -1,19 +1,32 @@
<sect2 id="config-LSauthMethod_HTTP">
<title>LSauthMethod_HTTP</title>
<para>Cette &LSauthMethod; est utilisée pour gérer l'authentification
via les variables d'environnements définies suite à une authentification
gérée par le serveur HTTP. En &php;, ces informations sont consultables
via les variables <literal>$_SERVER['PHP_AUTH_USER']</literal> et
<literal>$_SERVER['PHP_AUTH_PW']</literal>. Si la variable
<literal>$_SERVER['PHP_AUTH_USER']</literal> est présente, une recherche
dans l'annuaire est effectué pour trouver l'utilisateur correspondant.
L'authentification réussie uniquement si un et un seul utilisateur est
retourné par la recherche et si une authentification auprès de l'annuaire
LDAP réussie à l'aide du DN de l'objet LDAP trouvé et du mot de passe issu
de la variable <literal>$_SERVER['PHP_AUTH_PW']</literal>.</para>
via les variables d'environnements définies suite à une authentification,
potentiellement déléguée au serveur web.</para>
<para>Cette méthode récupère dans l'environment d'exécution PHP, le nom
d'utilisateur et le mot de passe de l'utilisateur connecté. À partir du
nom d'utilisateur, une recherche dans l'annuaire sera effectuée pour
trouver l'utilisateur correspondant. L'authentification sera réussie
uniquement si un et un seul utilisateur est retourné par la recherche et
si une authentification auprès de l'annuaire LDAP réussie à l'aide du DN
de l'objet LDAP trouvé et du mot de passe fourni.</para>
<note><simpara>En cas d'authentification déléguée au serveur web, il est
possible de désactiver la vérification du mot de passe via le paramètre
<literal>LSAUTHMETHOD_HTTP_TRUST_WITHOUT_PASSWORD_CHALLENGE</literal>
(voir ci-dessous).</simpara></note>
<para>Les variables d'environnements utilisées pour authentifier l'utilisateur
connecté dépendent de la méthode configurée via la constante <literal>
LSAUTHMETHOD_HTTP_METHOD</literal> (voir ci-dessous). Si ces variables ne sont
pas disponibles, une erreur HTTP 403 sera générée pour réclamer une
authentification à l'utilisateur.</para>
<note><simpara>Cette &LSauthMethod; supporte le mode API et il s'agit de la
méthode utilisée par défaut dans ce mode.</simpara></note>
<para>Cette librairie peut être configurée en éditant le fichier de
configiration
configuration
<literal>conf/LSauth/config.LSauthMethod_HTTP.php</literal>.</para>
<programlisting linenumbering="unnumbered">
@ -25,6 +38,9 @@
// Don't check HTTP server's login/password by LDAP authentication challenge
//define('LSAUTHMETHOD_HTTP_TRUST_WITHOUT_PASSWORD_CHALLENGE',true);
// Authentication realm (API mode only)
//define('LSAUTHMETHOD_HTTP_API_REALM', ___('LdapSaisie API - Authentication required'));
</programlisting>
<!-- Début Paramètres Configuration -->
@ -43,7 +59,7 @@
<varlistentry>
<term>LSAUTHMETHOD_HTTP_METHOD</term>
<listitem>
<para>Permet de définir la méthode utilisée par le serveur HTTP pour passer
<para>Permet de définir la méthode utilisée par le serveur web pour passer
à PHP l'identifiant de l'utilisateur connecté et son mot de passe.</para>
<para>Cette constance peut pendre les valeurs suivantes :
<variablelist>
@ -51,7 +67,7 @@
<varlistentry>
<term>PHP_PASS</term>
<listitem>
<para>Dans cette méthode, le serveur HTTP défini les variables
<para>Dans cette méthode, le serveur web défini les variables
d'environnement <literal>PHP_AUTH_USER</literal> et <literal>
PHP_AUTH_PW</literal>. Cette méthode est la méthode par défaut et
convient en cas d'utilisation de <literal>mod_php</literal>.</para>
@ -61,7 +77,7 @@
<varlistentry>
<term>REMOTE_USER</term>
<listitem>
<para>Dans cette méthode, le serveur HTTP défini la variable
<para>Dans cette méthode, le serveur web défini la variable
d'environnement <literal>REMOTE_USER</literal>. Cette variable ne contient
que l'identifiant de l'utilisateur connecté. Cette méthode ne peut donc
être utilisée que conjointement avec l'activation du paramètre
@ -73,12 +89,12 @@
<varlistentry>
<term>AUTHORIZATION</term>
<listitem>
<para>Dans cette méthode, le serveur HTTP passe le contenu de l'entête
<para>Dans cette méthode, le serveur web passe le contenu de l'entête
HTTP <literal>Authorization</literal> dans la variable d'environnement
<literal>HTTP_AUTHORIZATION</literal>. Cette méthode convient en cas d'
utilisation de PHP en mode CGI ou encore via PHP-FPM.</para>
<para>Pour utiliser cette méthode, il faudra adapter la configuration du
serveur HTTP. Par exemple, pour Apache HTTPd, vous pouvez utiliser le
serveur web. Par exemple, pour Apache HTTPd, vous pouvez utiliser le
module <literal>rewrite</literal> et la règle de réécriture suivante :
<programlisting linenumbering="unnumbered">
<![CDATA[RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]]]>
@ -104,5 +120,15 @@
</listitem>
</varlistentry>
<varlistentry>
<term>LSAUTHMETHOD_HTTP_REALM</term>
<listitem>
<para>Domaine d'authentification (<literal>reaml</literal>) utilisé pour
réclamer l'authentification de l'utilisateur (facultatif).</para>
<note><simpara>Pour que le message soit traduit, utilisez la fonction
<literal>___()</literal> (voir exemple).</simpara></note>
</listitem>
</varlistentry>
</variablelist>
</sect2>

View file

@ -19,11 +19,14 @@ serveur LDAP.</para>
'useUserCredentials' => [boolean],
'LSauth' => array (
'method' => [LSauth method],
'api_method' => [LSauth method],
'LSobjects' => array(
'[object type 1]',
'[object type 2]' => array(
'filter' => '[LDAP filter]',
'password_attribute' => '[attribute name]',
'web_access' => [booléen],
'api_access' => [booléen],
)
)
),
@ -120,6 +123,18 @@ serveur LDAP.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>api_method</term>
<listitem>
<simpara>Nom de la méthode d'authentification &LSauthMethod; à utilisée lors d'une connexion à
l'API. Exemple : pour utiliser la classe <literal>LSauthMethod_HTTP</literal>, la valeur de ce
paramètre sera <literal>HTTP</literal>. <emphasis>Paramètre facultatif, méthode par défaut :
<literal>HTTP</literal>.</emphasis></simpara>
<warning><simpara>Toutes les &LSauthMethod; ne supportent pas forcément le mode API.</simpara>
</warning>
</listitem>
</varlistentry>
<varlistentry>
<term>LSobjects</term>
<listitem>
@ -151,6 +166,22 @@ serveur LDAP.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>web_access</term>
<listitem>
<simpara>Permet de définir si ce type d'objet à le droit d'utiliser l'interface web (facultatif,
par défaut : <literal>True</literal>).</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>api_access</term>
<listitem>
<simpara>Permet de définir si ce type d'objet à le droit d'utiliser l'API (facultatif,
par défaut : <literal>False</literal>).</simpara>
</listitem>
</varlistentry>
</variablelist>
</listitem>
</varlistentry>

View file

@ -49,3 +49,6 @@
// Remote logout URL (in SSO context for instance)
//define('LSAUTHMETHOD_HTTP_LOGOUT_REMOTE_URL', 'https://idp.domain.tld/logout');
// Authentication realm
//define('LSAUTHMETHOD_HTTP_REALM', ___('LdapSaisie - Authentication required'));

View file

@ -50,12 +50,16 @@ $GLOBALS['LSconfig'] = array(
),
'LSauth' => array (
//'method' => 'basic', // Auth method : basic(default), HTTP, CAS or anonymous
//'api_method' => 'HTTP', // Auth method that must support API mode : HTTP(default) or anonymous
'LSobjects' => array(
'LSpeople' => array(
'filter' => '(|(uid=%{user})(mail=%{user}))',
'password_attribute' => 'userPassword',
'api_access' => false,
),
'LSsysaccount',
'LSsysaccount' => array(
'api_access' => true,
)
),
//'allow_multi_match' => false, // Allow username multiple match (default: false)
),

View file

@ -34,6 +34,7 @@ class LSauth extends LSlog_staticLoggerClass {
static private $authData=NULL;
static private $authObject=NULL;
static private $config=array();
static private $method=NULL;
static private $provider=NULL;
static private $params = array (
@ -52,21 +53,28 @@ class LSauth extends LSlog_staticLoggerClass {
self :: log_debug('Failed to load LSauthMethod class');
return;
}
if (!isset(self :: $config['method'])) {
self :: $config['method']='basic';
}
$class='LSauthMethod_'.self :: $config['method'];
$api_mode = LSsession :: get('api_mode');
if ($api_mode)
self :: $method = self :: getConfig('api_method', 'HTTP');
else
self :: $method = self :: getConfig('method', 'basic');
$class = "LSauthMethod_".self :: $method;
self :: log_debug('provider -> '.$class);
if (LSsession :: loadLSclass($class)) {
if ($api_mode && !$class :: apiModeSupported()) {
LSerror :: addErrorCode('LSauth_08', self :: $method);
return;
}
self :: $provider = new $class();
if (!self :: $provider) {
LSerror :: addErrorCode('LSauth_05',self :: $config['method']);
LSerror :: addErrorCode('LSauth_05', self :: $method);
return;
}
self :: log_debug('Provider Started !');
return true;
}
else {
LSerror :: addErrorCode('LSauth_04',self :: $config['method']);
LSerror :: addErrorCode('LSauth_04', self :: $method);
return;
}
}
@ -107,19 +115,21 @@ class LSauth extends LSlog_staticLoggerClass {
public static function getAuthObjectTypes() {
$objTypes = array();
foreach(self :: getConfig('LSobjects', array()) as $objType => $objParams) {
if (!self :: checkAuthObjectTypeAccess($objType))
continue;
if (is_int($objType) && is_string($objParams)) {
// We just have the object type
$objTypes[$objParams] = array('filter' => null, 'password_attribute' => 'userPassword');
continue;
}
$objTypes[$objType] = array(
'filter' => self :: getConfig("LSobjects.$objType.filter", null, 'string'),
'password_attribute' => self :: getConfig("LSobjects.$objType.password_attribute", 'userPassword', 'string'),
);
}
// For retro-compatibility, also retreived old parameters:
// For retro-compatibility, also retreived old parameters (excepted in API mode):
$oldAuthObjectType = LSconfig :: get('authObjectType', null, 'string', LSsession :: $ldapServer);
if ($oldAuthObjectType && !array_key_exists($oldAuthObjectType, $objTypes)) {
if ($oldAuthObjectType && !array_key_exists($oldAuthObjectType, $objTypes) && self :: checkAuthObjectTypeAccess($oldAuthObjectType)) {
$objTypes[$oldAuthObjectType] = array(
'filter' => LSconfig :: get('authObjectFilter', null, 'string', LSsession :: $ldapServer),
'password_attribute' => LSconfig :: get('authObjectTypeAttrPwd', 'userPassword', 'string', LSsession :: $ldapServer),
@ -128,6 +138,22 @@ class LSauth extends LSlog_staticLoggerClass {
return $objTypes;
}
/**
* Check if the specified auth object type have acces to LdapSaisie (on the current mode)
*
* @param[in] $objType string The LSobject type
*
* @return boolean True if specified auth object type have acces to LdapSaisie, False otherwise
*/
public static function checkAuthObjectTypeAccess($objType) {
// Check Web/API access rights
if (LSsession :: get('api_mode')) {
return self :: getConfig("LSobjects.$objType.api_access", false, 'bool');
}
return self :: getConfig("LSobjects.$objType.web_access", true, 'bool');
}
/**
* Retreived LSobjects corresponding to a username
*
@ -299,3 +325,6 @@ ___("LSauth : Not correctly initialized.")
LSerror :: defineError('LSauth_07',
___("LSauth : Failed to get authentication informations from provider.")
);
LSerror :: defineError('LSauth_08',
___("LSauth : Method %{method} configured doesn't support API mode.")
);

View file

@ -29,7 +29,10 @@ LSsession :: loadLSclass('LSlog_staticLoggerClass');
*/
class LSauthMethod extends LSlog_staticLoggerClass {
var $authData = array();
protected $authData = array();
// Boolean flag to specify if this LSauthMethod support API mode
protected static $api_mode_supported = false;
public function __construct() {
// Load config (without warning if not found)
@ -115,4 +118,13 @@ class LSauthMethod extends LSlog_staticLoggerClass {
return false;
}
/**
* Check API mode support of this method
*
* @retval boolean True if API mode is support, false otherwise
*/
static public function apiModeSupported() {
return static :: $api_mode_supported;
}
}

View file

@ -29,6 +29,9 @@ LSsession :: loadLSclass('LSauthMethod_basic');
*/
class LSauthMethod_HTTP extends LSauthMethod_basic {
// Boolean flag to specify if this LSauthMethod support API mode
protected static $api_mode_supported = true;
public function __construct() {
parent :: __construct();
LSauth :: disableLoginForm();
@ -53,6 +56,7 @@ class LSauthMethod_HTTP extends LSauthMethod_basic {
self :: log_debug('HTTP method to retreive auth data is "'.LSAUTHMETHOD_HTTP_METHOD.'"');
}
$missing_info = null;
switch(constant('LSAUTHMETHOD_HTTP_METHOD')) {
case 'AUTHORIZATION':
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION'])) {
@ -62,11 +66,12 @@ class LSauthMethod_HTTP extends LSauthMethod_basic {
'username' => $authData[0],
'password' => $authData[1],
);
}
return $this -> authData;
}
else
LSerror :: addErrorCode('LSauthMethod_HTTP_01', 'HTTP_AUTHORIZATION');
self :: log_warning("Fail to decode and parse $missing_info environment variable.");
}
$missing_info = 'HTTP_AUTHORIZATION';
break;
case 'REMOTE_USER':
if (isset($_SERVER['REMOTE_USER']) && !empty($_SERVER['REMOTE_USER'])) {
@ -76,8 +81,7 @@ class LSauthMethod_HTTP extends LSauthMethod_basic {
);
return $this -> authData;
}
else
LSerror :: addErrorCode('LSauthMethod_HTTP_01', 'REMOTE_USER');
$missing_info = 'REMOTE_USER';
break;
case 'PHP_AUTH':
default:
@ -88,10 +92,20 @@ class LSauthMethod_HTTP extends LSauthMethod_basic {
);
return $this -> authData;
}
else
LSerror :: addErrorCode('LSauthMethod_HTTP_01', 'PHP_AUTH_USER');
$missing_info = 'PHP_AUTH_USER';
}
return;
self :: log_warning("$missing_info variable not available in environment, trigger 403 error.");
// Auth data not available, trigger HTTP 403 error
if (defined('LSAUTHMETHOD_HTTP_REALM') && constant('LSAUTHMETHOD_HTTP_REALM'))
$realm = __(LSAUTHMETHOD_HTTP_REALM);
else
$realm = _('LdapSaisie - Authentication required');
header('WWW-Authenticate: Basic realm="'.$realm.'", charset="UTF-8"');
header('HTTP/1.0 401 Unauthorized');
LSerror :: addErrorCode(null, $realm);
LSsession :: displayAjaxReturn();
exit();
}
/**

View file

@ -27,6 +27,9 @@
*/
class LSauthMethod_anonymous extends LSauthMethod {
// Boolean flag to specify if this LSauthMethod support API mode
protected static $api_mode_supported = true;
public function __construct() {
LSauth :: disableLoginForm();
LSauth :: disableSelfAccess();

View file

@ -123,13 +123,17 @@ class LSerror {
*
* @retvat string Le texte des erreurs
*/
public static function getErrors() {
public static function getErrors($raw=false) {
if(!empty($_SESSION['LSerror'])) {
$txt = '';
if ($raw)
$return = $_SESSION['LSerror'];
else {
$return = '';
foreach ($_SESSION['LSerror'] as $error)
$txt .= $error."<br />\n";
$return .= $error."<br />\n";
}
self :: resetError();
return $txt;
return $return;
}
return;
}

View file

@ -52,6 +52,10 @@ class LSform extends LSlog_staticLoggerClass {
var $warnings = array();
var $api_mode = false;
private $submited = false;
/**
* Constructeur
*
@ -64,7 +68,7 @@ class LSform extends LSlog_staticLoggerClass {
*
* @retval void
*/
public function __construct(&$ldapObject, $idForm, $submit=NULL){
public function __construct(&$ldapObject, $idForm, $submit=NULL, $api_mode=false){
$this -> idForm = $idForm;
if (!$submit) {
$this -> submit = _("Validate");
@ -72,6 +76,7 @@ class LSform extends LSlog_staticLoggerClass {
else {
$this -> submit = $submit;
}
$this -> api_mode = $api_mode;
$this -> ldapObject =& $ldapObject;
$this -> config = $ldapObject -> getConfig('LSform');
LSsession :: loadLSclass('LSformElement');
@ -428,11 +433,22 @@ class LSform extends LSlog_staticLoggerClass {
* @retval boolean true si la saisie du formulaire est présente en POST, false sinon
*/
public function isSubmit() {
if ($this -> submited)
return true;
if( (isset($_POST['validate']) && ($_POST['validate']=='LSform')) && (isset($_POST['idForm']) && ($_POST['idForm'] == $this -> idForm)) )
return true;
return;
}
/**
* Set form as submited
*
* @retval void
*/
public function setSubmited() {
$this -> submited = true;
}
/**
* Défini arbitrairement des données en POST
*

View file

@ -403,4 +403,18 @@ class LSformElement extends LSlog_staticLoggerClass {
$opts[] = $opt;
}
/**
* Retreive value as return in API response
*
* @retval mixed API value(s) or null/empty array if no value
*/
public function getApiValue() {
if ($this -> isMultiple()) {
return ensureIsArray($this -> values);
}
if (!$this -> values)
return null;
return $this -> values[0];
}
}

View file

@ -223,9 +223,14 @@ class LSformElement_date extends LSformElement {
return true;
}
$values = self :: getData($_POST, $this -> name);
$special_values = self :: getData($_POST, $this -> name.'__special_value');
self :: log_trace($this." -> getPostData(): values=".varDump($values));
if ($this -> form -> api_mode) {
$special_values = false;
}
else {
$special_values = self :: getData($_POST, $this -> name.'__special_value');
self :: log_trace($this." -> getPostData(): special_values=".varDump($special_values));
}
if (!is_array($values) && !is_array($special_values)) {
self :: log_trace($this." -> getPostData(): not in POST data");
if ($onlyIfPresent) {

View file

@ -163,4 +163,21 @@ class LSformElement_image extends LSformElement {
}
return _("An unknown error occured sending this file.");
}
/**
* Retreive value as return in API response
*
* @retval mixed API value(s) or null/empty array if no value
*/
public function getApiValue() {
if ($this -> isMultiple()) {
$values = array();
for ($i=0; $i < count($this -> values); $i++)
$values[] = base64_encode($this -> values[0]);
return $values;
}
if (!$this -> values)
return null;
return base64_encode($this -> values[0]);
}
}

View file

@ -66,6 +66,29 @@ class LSformElement_jsonCompositeAttribute extends LSformElement {
*/
var $components = array();
/**
* Parse values
*
* @retval array Parsed values
*/
private function parseValues() {
self :: log_trace('values: '.varDump($this -> values));
$parseValues=array();
foreach($this -> values as $val) {
$decodedValue = json_decode($val, true);
self :: log_trace('decoded value: '.varDump($decodedValue));
if (is_array($decodedValue)) {
$parseValue = array('value' => $val);
foreach($decodedValue as $c => $cvalue) {
$parseValue[$c] = $this -> translateComponentValue($c,$cvalue);
}
$parseValues[] = $parseValue;
}
}
self :: log_trace('parsed values: '.varDump($parseValues));
return $parseValues;
}
/**
* Retourne les infos d'affichage de l'élément
*
@ -76,25 +99,9 @@ class LSformElement_jsonCompositeAttribute extends LSformElement {
public function getDisplay(){
$return = $this -> getLabelInfos();
$parseValues=array();
$invalidValues=array();
foreach($this -> values as $val) {
$decodedValue=json_decode($val, true);
if (is_array($decodedValue)) {
$parseValue=array('value' => $val);
foreach($decodedValue as $c => $cvalue) {
$parseValue[$c]=$this -> translateComponentValue($c,$cvalue);
}
$parseValues[]=$parseValue;
}
else {
$invalidValues[]=$val;
}
}
$return['html'] = $this -> fetchTemplate(NULL,
array(
'parseValues' => $parseValues,
'parseValues' => $this -> parseValues(),
'fullWidth' => $this -> getParam('html_options.fullWidth', false, 'bool'),
)
);
@ -181,7 +188,14 @@ class LSformElement_jsonCompositeAttribute extends LSformElement {
protected function getSelectListComponentPossibleValues($c) {
if (!isset($this -> _cache_getSelectListComponentPossibleValues[$c])) {
if (!LSsession :: loadLSclass('LSattr_html_select_list')) return;
$this -> _cache_getSelectListComponentPossibleValues[$c]=LSattr_html_select_list :: _getPossibleValues($this -> components[$c]['options'], $this -> name, $this->attr_html->attribute->ldapObject);
$this -> _cache_getSelectListComponentPossibleValues[$c] = LSattr_html_select_list :: _getPossibleValues(
$this -> components[$c]['options'],
$this -> name,
$this->attr_html->attribute->ldapObject
);
self :: log_trace(
"Component $c possible values: ".varDump($this -> _cache_getSelectListComponentPossibleValues[$c])
);
}
return $this -> _cache_getSelectListComponentPossibleValues[$c];
}
@ -205,6 +219,7 @@ class LSformElement_jsonCompositeAttribute extends LSformElement {
if ($v == $value) return $label;
}
}
self :: log_trace("No label found for value '$value'");
return;
}
@ -225,78 +240,241 @@ class LSformElement_jsonCompositeAttribute extends LSformElement {
return true;
}
$return[$this -> name]=array();
if (is_array($_POST[$this -> name.'__values_uuid'])) {
foreach ($_POST[$this -> name.'__values_uuid'] as $uuid) {
$value=array();
// Extract value form POST data
$parseValues = array();
// API mode
if ($this -> form -> api_mode) {
$json_values = $this -> getData($_POST, $this -> name);
if (!is_array($json_values) || empty($json_values)) {
self :: log_trace($this." -> getPostData(): not in POST data");
return true;
}
$json_value_count = 0;
foreach($json_values as $json_value) {
$json_value_count += 1;
$input_value = json_decode($json_value, true);
if (!is_array($input_value)) {
$this -> form -> setElementError(
$this -> attr_html,
getFData(_('Fail to decode JSON value #%{idx}.'), $json_value_count)
);
continue;
}
$parseValue = array();
$errors=array();
$unemptyComponents = array();
foreach ($this -> components as $c => $cconf) {
if (isset($_POST[$this -> name.'__'.$c.'__'.$uuid])) {
if (!is_array($_POST[$this -> name.'__'.$c.'__'.$uuid]))
$_POST[$this -> name.'__'.$c.'__'.$uuid] = array($_POST[$this -> name.'__'.$c.'__'.$uuid]);
foreach (array_keys($this -> components) as $c) {
if (!isset($input_value[$c]))
continue;
if ($this -> getComponentConfig($c, 'multiple', false, 'bool')) {
$parseValue[$c] = array();
if (is_array($input_value[$c])) {
foreach($input_value[$c] as $val) {
if (is_empty($val))
continue;
$parseValue[$c][] = $val;
}
}
}
else {
$parseValue[$c] = $input_value[$c];
}
if (is_empty($parseValue[$c])) {
unset($parseValue[$c]);
continue;
}
$unemptyComponents[] = $c;
}
// Ignore empty value from form
if (empty($unemptyComponents))
continue;
$parseValues[] = $parseValue;
}
}
elseif (is_array($_POST[$this -> name.'__values_uuid'])) {
// HTML form mode
foreach ($_POST[$this -> name.'__values_uuid'] as $uuid) {
$parseValue = array();
$unemptyComponents = array();
foreach (array_keys($this -> components) as $c) {
if (!isset($_POST[$this -> name.'__'.$c.'__'.$uuid]))
continue;
$parseValue[$c] = array();
foreach($_POST[$this -> name.'__'.$c.'__'.$uuid] as $val) {
if (empty($val))
continue;
$parseValue[$c][] = $val;
if ($cconf['type']=='select_list') {
if (!$this -> getSelectListComponentValueLabel($c, $val)) {
$errors[]=getFData(_('Invalid value "%{value}" for component %{component}.'),array('value' => $val, 'component' => __($cconf['label'])));
}
}
if (isset($cconf['check_data']) && is_array($cconf['check_data'])) {
foreach($cconf['check_data'] as $ruleType => $rconf) {
$className='LSformRule_'.$ruleType;
if (LSsession::loadLSclass($className)) {
$r=new $className();
if (!$r -> validate($val,$rconf,$this)) {
if (isset($rconf['msg'])) {
$errors[]=getFData(__($rconf['msg']),__($cconf['label']));
}
else {
$errors[]=getFData(_('Invalid value "%{value}" for component %{component}.'),array('value' => $val, 'component' => __($cconf['label'])));
}
}
}
else {
$errors[]=getFData(_("Can't validate value of component %{c}."),__($cconf['label']));
}
}
}
}
if (empty($parseValue[$c]))
if (empty($parseValue[$c])) {
unset($parseValue[$c]);
continue;
if (!isset($cconf['multiple']) || !$cconf['multiple']) {
}
if (!$this -> getComponentConfig($c, 'multiple', false, 'bool')) {
$parseValue[$c] = $parseValue[$c][0];
}
$unemptyComponents[] = $c;
$value[$c]=$parseValue[$c];
}
// Ignore empty value from form
if (empty($unemptyComponents))
continue;
$parseValues[] = $parseValue;
}
}
if (!empty($unemptyComponents)) {
// Check extracted values
foreach ($parseValues as $parseValue) {
// Check component value
foreach ($parseValue as $c => $value)
$this -> checkComponentValues($c, $value);
// Check required components
foreach ($this -> components as $c => $cconf) {
if ($cconf['required'] && !isset($value[$c])) {
$errors[]=getFData(_('Component %{c} must be defined'),__($cconf['label']));
continue;
}
}
foreach($errors as $e) {
$this -> form -> setElementError($this -> attr_html,$e);
}
$return[$this -> name][]=json_encode($value);
foreach (array_keys($this -> components) as $c) {
if ($this -> getComponentConfig($c, 'required', false, 'bool') && !isset($parseValue[$c])) {
$this -> form -> setElementError(
$this -> attr_html,
getFData(
_('Component %{c} must be defined'),
__($this -> getComponentConfig($c, 'label'))
)
);
}
}
$return[$this -> name][] = json_encode($parseValue);
}
return true;
}
/**
* Check one component's values
*
* @param[] $c The component name
* @param[] $value The values of the component
*
* @retval void
**/
private function checkComponentValues($c, $value) {
if ($this -> getComponentConfig($c, 'multiple', false, 'bool')) {
foreach ($value as $val) {
$this -> checkComponentValue($c, $val);
}
}
else
$this -> checkComponentValue($c, $value);
}
/**
* Check one component's value
*
* @param[] $c The component name
* @param[] $value The value to check
*
* @retval void
**/
private function checkComponentValue($c, $value) {
$label = __($this -> getComponentConfig($c, 'label'));
// select_list components : check values
if ($this -> getComponentConfig($c, 'type') == 'select_list') {
if (!$this -> getSelectListComponentValueLabel($c, $value)) {
$this -> form -> setElementError(
$this -> attr_html,
getFData(
_('Invalid value "%{value}" for component %{component}.'),
array('value' => $value, 'component' => $label)
)
);
}
}
// Apply check data rules
foreach($this -> getComponentConfig($c, 'check_data', array(), 'array') as $ruleType => $rconf) {
$className = 'LSformRule_'.$ruleType;
if (LSsession::loadLSclass($className)) {
$r = new $className();
if (!$r -> validate($value, $rconf, $this)) {
if (isset($rconf['msg'])) {
$this -> form -> setElementError(
$this -> attr_html,
getFData(__($rconf['msg']), $label)
);
}
else {
$this -> form -> setElementError(
$this -> attr_html,
getFData(
_('Invalid value "%{value}" for component %{component}.'),
array('value' => $value, 'component' => $label)
)
);
}
}
}
else {
$this -> form -> setElementError(
$this -> attr_html,
getFData(_("Can't validate value of component %{c}."), $label)
);
}
}
}
/**
* Return a configuration parameter for a specific component (or default value)
*
* @param[] $component The component name
* @param[] $param The configuration parameter
* @param[] $default The default value (default : null)
* @param[] $cast Cast resulting value in specific type (default : disabled)
*
* @retval mixed The configuration parameter value or default value if not set
**/
public function getComponentConfig($component, $param, $default=null, $cast=null) {
return LSconfig :: get(
$param, $default, $cast,
(array_key_exists($component, $this -> components)?$this -> components[$component]:array())
);
}
/**
* Retreive value as return in API response
*
* @retval mixed API value(s) or null/empty array if no value
*/
public function getApiValue() {
$values = array();
foreach(ensureIsArray($this -> values) as $value) {
$decodedValue = json_decode($value, true);
if (is_array($decodedValue)) {
$parsedValue = array();
foreach(array_keys($this -> components) as $c) {
if (!isset($decodedValue[$c]))
continue;
if ($this -> getComponentConfig($c, 'multiple', false, 'bool')) {
$parsedValue[$c] = ensureIsArray($decodedValue[$c]);
}
else {
$parsedValue[$c] = $decodedValue[$c];
}
}
$values[] = $parsedValue;
}
}
if ($this -> isMultiple()) {
return $values;
}
if (!$values)
return null;
return $values[0];
}
}

View file

@ -117,26 +117,48 @@ class LSformElement_labeledValue extends LSformElement {
if($this -> isFreeze()) {
return true;
}
if (isset($_POST[$this -> name."_labels"]) && isset($_POST[$this -> name."_values"])) {
$return[$this -> name] = array();
// Extract value form POST data
$values = array();
// API mode
if ($this -> form -> api_mode) {
if (isset($_POST[$this -> name])) {
foreach(ensureIsArray($_POST[$this -> name]) as $val) {
if (is_empty($val))
continue;
$parseValue = self :: parseValue($val);
if (isset($parseValue['label']) && isset($parseValue['value'])) {
$values[] = '['.$parseValue['label'].']'.$parseValue['value'];
}
else {
$this -> form -> setElementError(
$this -> attr_html,
getFData(_('Invalid value : "%{value}".'), $val)
);
}
}
}
}
elseif (isset($_POST[$this -> name."_labels"]) && isset($_POST[$this -> name."_values"])) {
$_POST[$this -> name."_labels"] = ensureIsArray($_POST[$this -> name."_labels"]);
$_POST[$this -> name."_values"] = ensureIsArray($_POST[$this -> name."_values"]);
foreach($_POST[$this -> name."_labels"] as $key => $label) {
$val = $_POST[$this -> name."_values"][$key];
if (!empty($label) && !is_empty($val)) {
$return[$this -> name][$key] = "[$label]$val";
$values[$key] = "[$label]$val";
}
}
return true;
}
elseif ($onlyIfPresent) {
self :: log_debug($this -> name.": not in POST data => ignore it");
return true;
}
else {
$return[$this -> name] = array();
return true;
}
}
if ($values) {
$return[$this -> name] = $values;
}
elseif ($onlyIfPresent) {
self :: log_debug($this -> name.": not in POST data => ignore it");
}
else {
$return[$this -> name] = array();
}
return true;
}
}

View file

@ -41,6 +41,41 @@ class LSformElement_mailQuota extends LSformElement {
1000000000 => 'Go'
);
/**
* Parse one value
*
* @param[in] $value string The value to parse
*
* @retval array Parsed value
*/
public function parseValue($value) {
if (preg_match('/^([0-9]+)'.$this -> getSuffix().'$/',$value,$regs)) {
$infos = array(
'size' => $regs[1],
);
if ($infos['size'] == 0) {
return array(
'size' => 0,
'valueSize' => 0,
'valueSizeFact' => 1,
'valueTxt' => "0",
);
}
krsort($this -> sizeFacts, SORT_NUMERIC);
foreach($this -> sizeFacts as $fact => $unit) {
if ($infos['size'] >= $fact) {
$infos['valueSize'] = round($infos['size'] / $fact, 2);
$infos['valueSizeFact'] = $fact;
$infos['valueTxt'] = $infos['valueSize'].$unit;
break;
}
}
ksort($this -> sizeFacts, SORT_NUMERIC);
return $infos;
}
return false;
}
/**
* Retourne les infos d'affichage de l'élément
*
@ -54,26 +89,9 @@ class LSformElement_mailQuota extends LSformElement {
$quotas=array();
foreach ($this -> values as $value) {
if (preg_match('/([0-9]*)/'.$this -> getSuffix(),$value,$regs)) {
$infos = array(
'size' => $regs[1]
);
if ($infos['size'] >= 1000000000) {
$infos['valueSizeFact']=1000000000;
}
else if ($infos['size'] >= 1000000) {
$infos['valueSizeFact']=1000000;
}
else if ($infos['size'] >= 1000) {
$infos['valueSizeFact']=1000;
}
else {
$infos['valueSizeFact']=1;
}
$infos['valueSize'] = $infos['size'] / $infos['valueSizeFact'];
$infos['valueTxt'] = $infos['valueSize'].$this ->sizeFacts[$infos['valueSizeFact']];
$quotas[$value] = $infos;
$parsed_value = $this -> parseValue($value);
if ($parsed_value) {
$quotas[$value] = $parsed_value;
}
else {
$quotas[$value] = array(
@ -137,36 +155,69 @@ class LSformElement_mailQuota extends LSformElement {
if($this -> isFreeze()) {
return true;
}
if (isset($_POST[$this -> name.'_size'])) {
$return[$this -> name]=array();
$values = array();
if ($this -> form -> api_mode) {
if (isset($_POST[$this -> name])) {
foreach(ensureIsArray($_POST[$this -> name]) as $value) {
if ($this -> parseValue($value) !== false) {
$values[] = $value;
}
else {
$this -> form -> setElementError(
$this -> attr_html,
getFData(_('Invalid value : "%{value}".'), $value)
);
}
}
}
}
elseif (isset($_POST[$this -> name.'_size'])) {
$_POST[$this -> name.'_size'] = ensureIsArray($_POST[$this -> name.'_size']);
if(isset($_POST[$this -> name.'_sizeFact']) && !is_array($_POST[$this -> name.'_sizeFact'])) {
$_POST[$this -> name.'_sizeFact'] = array($_POST[$this -> name.'_sizeFact']);
}
foreach($_POST[$this -> name.'_size'] as $key => $val) {
if (!empty($val)) {
if (empty($val))
continue;
$f = 1;
if (isset($_POST[$this -> name.'_sizeFact'][$key]) && ($_POST[$this -> name.'_sizeFact'][$key]!=1)) {
$f = $_POST[$this -> name.'_sizeFact'][$key];
}
$return[$this -> name][$key] = ($val*$f).$this->getSuffix();
}
}
return true;
}
// Accept raw value to make import easier
elseif (isset($_POST[$this -> name])) {
$return[$this -> name]=$_POST[$this -> name];
return true;
}
elseif ($onlyIfPresent) {
self :: log_debug($this -> name.": not in POST data => ignore it");
return true;
}
else {
$return[$this -> name] = array();
return true;
$values[$key] = ($val*$f).$this->getSuffix();
}
}
if ($values) {
$return[$this -> name] = $values;
}
elseif ($onlyIfPresent) {
self :: log_debug($this -> name.": not in POST data => ignore it");
}
else {
$return[$this -> name] = array();
}
return true;
}
/**
* Retreive value as return in API response
*
* @retval mixed API value(s) or null/empty array if no value
*/
public function getApiValue() {
$values = array();
foreach(ensureIsArray($this -> values) as $value) {
$parsed_value = $this -> parseValue($value);
if (is_array($parsed_value)) {
$values[] = $parsed_value['size'];
}
}
if ($this -> isMultiple()) {
return $values;
}
if (!$values)
return null;
return $values[0];
}
}

View file

@ -89,7 +89,7 @@ class LSformElement_maildir extends LSformElement_text {
$retval = parent :: getPostData($return);
// Si une valeur est recupérée
if ($retval&&$_POST['LSformElement_maildir_'.$this -> name.'_do']) {
if ($retval && !$this -> form -> api_mode && $_POST['LSformElement_maildir_'.$this -> name.'_do']) {
$cur = $this -> form -> ldapObject -> attrs[$this -> name] -> getValue();
$cur=$cur[0];
$new = $return[$this -> name][0];

View file

@ -62,7 +62,7 @@ class LSformElement_password extends LSformElement {
return true;
}
if ($this -> getParam('html_options.confirmInput', False, 'bool')) {
if (!$this -> form -> api_mode && $this -> getParam('html_options.confirmInput', False, 'bool')) {
$confirm_data = self :: getData($_POST, $this -> name . '_confirm');
$confirmed = false;
if (!is_array($confirm_data)) {

View file

@ -38,9 +38,46 @@ class LSformElement_quota extends LSformElement {
1 => 'o',
1024 => 'Ko',
1048576 => 'Mo',
1073741824 => 'Go'
1073741824 => 'Go',
1099511627776 => 'To',
);
/**
* Parse one value
*
* @param[in] $value string The value to parse
*
* @retval array Parsed value
*/
public function parseValue($value) {
if (preg_match('/^([0-9]+)$/', $value, $regs)) {
$infos = array(
'size' => ceil($regs[1]/$this -> getFactor()),
);
if ($infos['size'] == 0) {
return array(
'size' => 0,
'valueSize' => 0,
'valueSizeFact' => 1,
'valueTxt' => "0",
);
}
krsort($this -> sizeFacts, SORT_NUMERIC);
foreach($this -> sizeFacts as $fact => $unit) {
if ($infos['size'] >= $fact) {
$infos['valueSize'] = round($infos['size'] / $fact, 2);
$infos['valueSizeFact'] = $fact;
$infos['valueTxt'] = $infos['valueSize'].$unit;
break;
}
}
ksort($this -> sizeFacts, SORT_NUMERIC);
return $infos;
}
return false;
}
/**
* Retourne les infos d'affichage de l'élément
*
@ -54,26 +91,9 @@ class LSformElement_quota extends LSformElement {
$quotas=array();
foreach ($this -> values as $value) {
if (preg_match('/^([0-9]*)$/',$value,$regs)) {
$infos = array(
'size' => ceil($regs[1]/$this -> getFactor())
);
if ($infos['size'] >= 1073741824) {
$infos['valueSizeFact']=1073741824;
}
else if ($infos['size'] >= 1048576) {
$infos['valueSizeFact']=1048576;
}
else if ($infos['size'] >= 1024) {
$infos['valueSizeFact']=1024;
}
else {
$infos['valueSizeFact']=1;
}
$infos['valueSize'] = $infos['size'] / $infos['valueSizeFact'];
$infos['valueTxt'] = $infos['valueSize'].$this ->sizeFacts[$infos['valueSizeFact']];
$quotas[$value] = $infos;
$parsed_value = $this -> parseValue($value);
if ($parsed_value) {
$quotas[$value] = $parsed_value;
}
else {
$quotas[$value] = array(
@ -128,36 +148,71 @@ class LSformElement_quota extends LSformElement {
if($this -> isFreeze()) {
return true;
}
if (isset($_POST[$this -> name.'_size'])) {
$return[$this -> name]=array();
$values = array();
if ($this -> form -> api_mode) {
if (isset($_POST[$this -> name])) {
foreach(ensureIsArray($_POST[$this -> name]) as $value) {
$values[] = ceil($value*$this -> getFactor());
}
}
}
elseif (isset($_POST[$this -> name.'_size'])) {
$_POST[$this -> name.'_size'] = ensureIsArray($_POST[$this -> name.'_size']);
if(isset($_POST[$this -> name.'_sizeFact']) && !is_array($_POST[$this -> name.'_sizeFact'])) {
$_POST[$this -> name.'_sizeFact'] = array($_POST[$this -> name.'_sizeFact']);
}
foreach($_POST[$this -> name.'_size'] as $key => $val) {
if (!empty($val)) {
if (empty($val))
continue;
$f = 1;
if (isset($_POST[$this -> name.'_sizeFact'][$key]) && ($_POST[$this -> name.'_sizeFact'][$key]!=1)) {
$f = $_POST[$this -> name.'_sizeFact'][$key];
}
$val = preg_replace('/,/', '.', $val);
$return[$this -> name][$key] = ceil(ceil(($val*$f)*$this -> getFactor()));
$values[$key] = ceil(ceil(($val*$f)*$this -> getFactor()));
}
}
return true;
if ($values) {
$return[$this -> name] = $values;
}
elseif ($onlyIfPresent) {
self :: log_debug($this -> name.": not in POST data => ignore it");
return true;
}
else {
$return[$this -> name] = array();
}
return true;
}
}
/**
* Retreive factor value
*
* @retval integer Factor value
*/
private function getFactor() {
return $this -> getParam('html_options.factor', 1);
return $this -> getParam('html_options.factor', 1, 'int');
}
/**
* Retreive value as return in API response
*
* @retval mixed API value(s) or null/empty array if no value
*/
public function getApiValue() {
$values = array();
foreach(ensureIsArray($this -> values) as $value) {
$parsed_value = $this -> parseValue($value);
if (is_array($parsed_value)) {
$values[] = $parsed_value['size'];
}
}
if ($this -> isMultiple()) {
return $values;
}
if (!$values)
return null;
return $values[0];
}
}

View file

@ -184,8 +184,8 @@ class LSformElement_supannCompositeAttribute extends LSformElement {
* @retval boolean true si la valeur est présente en POST, false sinon
*/
public function getPostData(&$return, $onlyIfPresent=false) {
if ($onlyIfPresent) {
self :: log_warning("getPostData : does not support \$onlyIfPresent mode => Post data ignored");
if ($onlyIfPresent || $this -> form -> api_mode) {
self :: log_warning("getPostData : does not support \$onlyIfPresent / API mode => Post data ignored");
return true;
}

View file

@ -157,6 +157,19 @@ class LSformElement_valueWithUnit extends LSformElement {
if($this -> isFreeze()) {
return true;
}
if ($this -> form -> api_mode) {
if (isset($_POST[$this -> name]) && $_POST[$this -> name]) {
$return[$this -> name] = array();
foreach(ensureIsArray($_POST[$this -> name]) as $value) {
if ($this -> getParam('html_options.store_integer')) {
$value = ($this -> getParam('html_options.round_down')?floor($value):ceil($value));
}
$return[$this -> name][] = $value;
}
}
}
else {
$return[$this -> name] = array();
if (isset($_POST[$this -> name.'_valueWithUnit'])) {
$_POST[$this -> name.'_valueWithUnit'] = ensureIsArray($_POST[$this -> name.'_valueWithUnit']);
@ -164,36 +177,50 @@ class LSformElement_valueWithUnit extends LSformElement {
$_POST[$this -> name.'_unitFact'] = array($_POST[$this -> name.'_unitFact']);
}
foreach($_POST[$this -> name.'_valueWithUnit'] as $key => $val) {
if (!empty($val)) {
if (empty($val))
continue;
$f = 1;
if (isset($_POST[$this -> name.'_unitFact'][$key]) && ($_POST[$this -> name.'_unitFact'][$key]!=1)) {
$f = $_POST[$this -> name.'_unitFact'][$key];
}
if ($this -> getParam('html_options.store_integer')) {
if ($this -> getParam('html_options.round_down')) {
$return[$this -> name][$key] = floor($val*$f);
}
else {
$return[$this -> name][$key] = ceil($val*$f);
}
$return[$this -> name][$key] = (
$this -> getParam('html_options.round_down')?
floor($val*$f):
ceil($val*$f)
);
}
else {
$return[$this -> name][$key] = ($val*$f);
}
}
}
}
if (isset($_POST[$this -> name])) {
$_POST[$this -> name] = ensureIsArray($_POST[$this -> name]);
$return[$this -> name] = array_merge($return[$this -> name], $_POST[$this -> name]);
}
if (isset($_POST[$this -> name.'_value'])) {
$_POST[$this -> name.'_value'] = ensureIsArray($_POST[$this -> name.'_value']);
$return[$this -> name] = array_merge($return[$this -> name], $_POST[$this -> name.'_value']);
}
}
return true;
}
/**
* Retreive value as return in API response
*
* @retval mixed API value(s) or null/empty array if no value
*/
public function getApiValue() {
$values = array();
foreach (ensureIsArray($this -> values) as $value)
if (preg_match('/^([0-9]*)$/', $value, $regs))
$values[] = intval($regs[1]);
if ($this -> isMultiple()) {
return $values;
}
if (!$values)
return null;
return $values[0];
}
}
/*

View file

@ -0,0 +1,86 @@
<?php
/*******************************************************************************
* Copyright (C) 2007 Easter-eggs
* http://ldapsaisie.labs.libre-entreprise.org
*
* Author: See AUTHORS file in top-level directory.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
******************************************************************************/
/**
* LSformRule to check rlcMemberDetails attribute value
*
* @author Benjamin Renard <brenard@easter-eggs.com>
*/
class LSformRule_rlcMemberDetails extends LSformRule {
// Validate values one by one or all together
public const validate_one_by_one = False;
/**
* Validate rlcMemberDetails attribute value
*
* @param string $values The value to validate
* @param array $options Validation options
* @param object $formElement The related formElement object
*
* @return boolean true if the value is valide, false if not
*/
public static function validate($value, $options=array(), &$formElement) {
$members = array();
foreach($value as $json_value) {
if (is_empty($json_value)) {
self :: log_error("validate($json_value): Empty value are not authorized");
return false;
}
$v = json_decode($json_value);
if (is_null($v)) {
self :: log_error("validate($json_value): fail to decode JSON value");
return false;
}
if (!isset($v->role) || is_empty($v->role) || !isset($v->uid) || is_empty($v->uid)) {
self :: log_error("validate($json_value): invalid value (no uid or role)");
return false;
}
if (!in_array($v->role, array('facilitator', 'governing_board_referent', 'contributor', 'observer'))) {
self :: log_error("validate($json_value): invalid role $v->role");
return false;
}
if (!array_key_exists($v->role, $members))
$members[$v->role] = array();
if (in_array($v->uid, $members[$v->role])) {
self :: log_error("validate($json_value): member $v->uid duplicated as $v->role");
return false;
}
$members[$v->role][] = $v->uid;
}
self :: log_debug("validate(): members: ".print_r($members, true));
foreach (array('facilitator', 'governing_board_referent', 'contributor') as $role) {
if (!array_key_exists($role, $members)) {
self :: log_error("validate(): no $role");
return false;
}
}
if (count($members['governing_board_referent']) > 1) {
self :: log_error("validate(): more than one governing_board_referent found");
return false;
}
return True;
}
}

View file

@ -170,8 +170,8 @@ class LSimport {
$globalErrors=array();
// Instanciate an LSobject
$object = new $LSobject();
// Instanciate a creation LSform
$form = $object -> getForm('create');
// Instanciate a creation LSform (in API mode)
$form = $object -> getForm('create', null, true);
// Set form data from inputed data
if ($form -> setPostData($objData,true)) {
// Validate form
@ -204,8 +204,8 @@ class LSimport {
// Instanciate a new LSobject and load data from it's DN
$object = new $LSobject();
if ($object -> loadData($dn)) {
// Instanciate a modify form
$form = $object -> getForm('modify');
// Instanciate a modify form (in API mode)
$form = $object -> getForm('modify', null, true);
// Set form data from inputed data
if ($form -> setPostData($objData,true)) {
// Validate form

View file

@ -199,21 +199,21 @@ class LSldapObject extends LSlog_staticLoggerClass {
}
/**
* Construit un formulaire de l'objet
* Constuct a form for this object
*
* Cette méthode construit un formulaire LSform à partir de la configuration de l'objet
* et de chaque attribut.
* This method create a LSform from the object and its attributes configurations.
*
* @param[in] $idForm [<b>required</b>] Identifiant du formulaire a créer
* @param[in] $load DN d'un objet similaire dont la valeur des attribut doit être chargé dans le formulaire.
* @param[in] $idForm string Form identifier (required)
* @param[in] $load string DN of a similar object. If defined, attributes values of this object will be loaded in the form.
* @param[in] $api_mode boolean Enable API mode (defaut: false)
*
* @author Benjamin Renard <brenard@easter-eggs.com>
*
* @retval LSform Le formulaire crée
* @retval LSform The created LSform object
*/
public function getForm($idForm,$load=NULL) {
public function getForm($idForm, $load=NULL, $api_mode=false) {
LSsession :: loadLSclass('LSform');
$LSform = new LSform($this,$idForm);
$LSform = new LSform($this, $idForm, null, $api_mode);
$this -> forms[$idForm] = array($LSform, $load);
if ($load) {
@ -1890,6 +1890,9 @@ class LSldapObject extends LSlog_staticLoggerClass {
}
return false;
}
elseif ($key=='type') {
return $this -> getType();
}
// Unknown key, log warning
self :: log_warning("__get($key): invalid property requested\n".LSlog :: get_debug_backtrace_context());
}
@ -2460,8 +2463,8 @@ class LSldapObject extends LSlog_staticLoggerClass {
if (is_null($objType) || empty($attrs_values))
LScli :: usage('You must provide LSobject type, DN and at least one change.');
// Instanciate a create LSform
$form = $obj -> getForm('create');
// Instanciate a create LSform (in API mode)
$form = $obj -> getForm('create', null, true);
// Check all changed attributes are in modify form and are'nn freezed
foreach ($attrs_values as $attr => $value) {
@ -2559,7 +2562,7 @@ class LSldapObject extends LSlog_staticLoggerClass {
elseif ($objType && class_exists($objType)) {
LScli :: need_ldap_con();
$obj = new $objType();
$form = $obj -> getForm('create');
$form = $obj -> getForm('create', null, true); // Instanciate create form in API mode
$form -> autocomplete_attrs_values($opts, $comp_word);
}
@ -2639,8 +2642,8 @@ class LSldapObject extends LSlog_staticLoggerClass {
return True;
}
// Instanciate a modify LSform
$form = $obj -> getForm('modify');
// Instanciate a modify LSform (in API mode)
$form = $obj -> getForm('modify', null, true);
// Check all changed attributes are in modify form and are'nn freezed
foreach ($changes as $attr => $value) {
@ -2742,7 +2745,7 @@ class LSldapObject extends LSlog_staticLoggerClass {
self :: log_error("Fail to load object $dn data from LDAP");
}
else {
$form = $obj -> getForm('modify');
$form = $obj -> getForm('modify', null, True); // Instanciate modify form in API mode
$form -> autocomplete_attrs_values($opts, $comp_word);
}
}

View file

@ -1181,7 +1181,8 @@ class LSsearch extends LSlog_staticLoggerClass {
$params = array (
'basedn',
'sortBy',
'sortDirection'
'sortDirection',
'attributes',
);
if ($key=='LSobject') {
return $this -> LSobject;
@ -1266,7 +1267,7 @@ class LSsearch extends LSlog_staticLoggerClass {
return $this -> getHash();
}
else {
throw new Exception('Incorrect property !');
throw new Exception("Incorrect property '$key'!");
}
}

View file

@ -99,6 +99,9 @@ class LSsession {
// List of currently loaded LSaddons
private static $loadedAddons = array();
// API mode
private static $api_mode = false;
/**
* Get session info by key
*
@ -128,6 +131,8 @@ class LSsession {
return self :: globalSearch();
case 'email_sender':
return self :: getEmailSender();
case 'api_mode':
return boolval(self :: $api_mode);
}
return null;
}
@ -1608,6 +1613,8 @@ class LSsession {
* @retval void
*/
public static function displayTemplate() {
if (self :: $api_mode)
return self :: displayAjaxReturn();
$KAconf = LSconfig :: get('keepLSsessionActive');
if (
(
@ -1671,54 +1678,79 @@ class LSsession {
}
/**
* Défini que l'affichage se fera ou non via un retour Ajax
* Set Ajax display mode
*
* @param[in] $val boolean True to enable Ajax display mode (optional, default: true)
*
* @param[in] $val boolean True pour que l'affichage se fasse par un retour
* Ajax, false sinon
* @retval void
*/
public static function setAjaxDisplay($val=true) {
self :: $ajaxDisplay = (boolean)$val;
}
/**
* Check if Ajax display mode is enabled
*
* @retval boolean True if Ajax display mode is enabled, False otherwise
*/
public static function getAjaxDisplay() {
return (boolean)self :: $ajaxDisplay;
}
/**
* Affiche un retour Ajax
*
* @retval void
*/
public static function displayAjaxReturn($data=array()) {
public static function displayAjaxReturn($data=array(), $pretty=false) {
if (isset($data['LSredirect']) && (!LSdebugDefined()) ) {
echo json_encode($data);
return;
}
if (class_exists('LStemplate'))
if (!self :: $api_mode && class_exists('LStemplate'))
$data['LSjsConfig'] = LStemplate :: getJSconfigParam();
// Infos
if((!empty($_SESSION['LSsession_infos']))&&(is_array($_SESSION['LSsession_infos']))) {
if (self :: $api_mode) {
$data['messages'] = $_SESSION['LSsession_infos'];
}
else {
$txt_infos="<ul>\n";
foreach($_SESSION['LSsession_infos'] as $info) {
$txt_infos.="<li>$info</li>\n";
}
$txt_infos.="</ul>\n";
$data['LSinfos'] = $txt_infos;
}
$_SESSION['LSsession_infos']=array();
}
if (LSerror :: errorsDefined()) {
$data['LSerror'] = LSerror :: getErrors();
$data[(self :: $api_mode?'errors':'LSerror')] = LSerror :: getErrors(self :: $api_mode);
}
if (isset($_REQUEST['imgload'])) {
if (!self :: $api_mode && isset($_REQUEST['imgload'])) {
$data['imgload'] = $_REQUEST['imgload'];
}
if (LSdebugDefined()) {
if (!self :: $api_mode && LSdebugDefined()) {
$data['LSdebug'] = LSdebug_print(true,false);
}
echo json_encode($data);
echo json_encode($data, (($pretty||isset($_REQUEST['pretty']))?JSON_PRETTY_PRINT:0));
}
/**
* Set API mode
*
* @param[in] $val boolean True to enable API mode (optional, default: true)
*
* @retval void
*/
public static function setApiMode($val=true) {
self :: $api_mode = (boolean)$val;
}
/**
@ -2306,6 +2338,22 @@ class LSsession {
return self :: canAccess($LSobject,NULL,'w','rdn');
}
/**
* Check user right to compute the result of a LSformat
*
* @param[in] $LSformat string The LSformat string to check
* @param[in] $LSobject string The LSobject type
* @param[in] $dn string The LSobject DN (optional, default: the container_dn of the LSobject type)
*
* @retval boolean True is user can compute the result of the LSformat, False otherwise
*/
public static function canComputeLSformat($LSformat, $LSobject, $dn=NULL) {
foreach (getFieldInFormat($LSformat) as $attr)
if (!self :: canAccess($LSobject, $dn, 'r', $attr))
return false;
return true;
}
/**
* Retourne le droit de l'utilisateur à gérer la relation d'objet
*

View file

@ -455,10 +455,22 @@ class LStemplate extends LSlog_staticLoggerClass {
* @retval void
**/
public static function fatal_error($error=null) {
http_response_code(500);
if (LSsession :: get('api_mode')) {
$errors = array(_("A fatal error occured. If problem persist, please contact support."));
if ($error)
$errors[] = $error;
echo json_encode(
array('errors' => $errors, 'success' => false),
(isset($_REQUEST['pretty'])?JSON_PRETTY_PRINT:0)
);
}
else {
self :: assign('pagetitle', _("A fatal error occured."));
self :: assign('error', _("A fatal error occured. If problem persist, please contact support."));
self :: assign('details', $error);
self :: display("error.tpl");
}
exit();
}

View file

@ -61,21 +61,23 @@ class LSurl extends LSlog_staticLoggerClass {
* @param[in] $handler callable The URL pattern handler (must be callable, required)
* @param[in] $authenticated boolean Permit to define if this URL is accessible only for authenticated users (optional, default: true)
* @param[in] $override boolean Allow override if a command already exists with the same name (optional, default: false)
* @param[in] $api_mode boolean Enable API mode (optional, default: false)
**/
public static function add_handler($pattern, $handler=null, $authenticated=true, $override=true) {
public static function add_handler($pattern, $handler=null, $authenticated=true, $override=true, $api_mode=false) {
if (is_array($pattern)) {
if (is_null($handler))
foreach($pattern as $p => $h)
self :: add_handlers($p, $h, $override);
self :: add_handler($p, $h, $override, $api_mode);
else
foreach($pattern as $p)
self :: add_handlers($p, $handler, $override);
self :: add_handler($p, $handler, $override, $api_mode);
}
else {
if (!isset(self :: $patterns[$pattern])) {
self :: $patterns[$pattern] = array(
'handler' => $handler,
'authenticated' => $authenticated,
'api_mode' => $api_mode,
);
}
elseif ($override) {
@ -83,6 +85,7 @@ class LSurl extends LSlog_staticLoggerClass {
self :: $patterns[$pattern] = array(
'handler' => $handler,
'authenticated' => $authenticated,
'api_mode' => $api_mode,
);
}
else {
@ -128,12 +131,15 @@ class LSurl extends LSlog_staticLoggerClass {
self :: redirect($default_url);
exit();
}
self :: log_debug("Current URL match with no pattern. Use error 404 handler.");
// Error 404
$api_mode = (strpos($current_url, 'api/') === 0);
self :: log_debug("Current URL match with no pattern. Use error 404 handler (API mode=$api_mode).");
return new LSurlRequest(
$current_url,
array(
'handler' => array('LSurl', 'error_404'),
'authenticated' => false,
'api_mode' => $api_mode,
)
);
}
@ -228,10 +234,18 @@ class LSurl extends LSlog_staticLoggerClass {
* @retval void
**/
public static function error_404($request=null) {
LStemplate :: assign('error', _("The requested page was not found."));
http_response_code(404);
$error = _("The requested page was not found.");
if (LSsession :: getAjaxDisplay() || ($request && $request->api_mode)) {
LSerror :: addErrorCode(null, $error);
LSsession :: displayAjaxReturn();
}
else {
LStemplate :: assign('error', $error);
LSsession :: setTemplate('error.tpl');
LSsession :: displayTemplate();
}
}
/**
* Handle the current requested URL
@ -249,7 +263,9 @@ class LSurl extends LSlog_staticLoggerClass {
self :: log_fatal(_("This request could not be handled."));
}
if (class_exists('LStemplate'))
if ($request -> api_mode)
LSsession :: setApiMode();
elseif (class_exists('LStemplate'))
LStemplate :: assign('request', $request);
// Check authentication (if need)

View file

@ -37,6 +37,9 @@ class LSurlRequest extends LSlog_staticLoggerClass {
// Request need authentication ?
private $authenticated = true;
// API mode enabled ?
private $api_mode = false;
// Parameters detected on requested URL
private $url_params = array();
@ -44,6 +47,7 @@ class LSurlRequest extends LSlog_staticLoggerClass {
$this -> current_url = $current_url;
$this -> handler = $handler_infos['handler'];
$this -> authenticated = (isset($handler_infos['authenticated'])?boolval($handler_infos['authenticated']):true);
$this -> api_mode = (isset($handler_infos['api_mode'])?boolval($handler_infos['api_mode']):false);
$this -> url_params = $url_params;
}
@ -61,6 +65,8 @@ class LSurlRequest extends LSlog_staticLoggerClass {
return $this -> handler;
if ($key == 'authenticated')
return $this -> authenticated;
if ($key == 'api_mode')
return $this -> api_mode;
if ($key == 'referer')
return $this -> get_referer();
if (array_key_exists($key, $this->url_params)) {

View file

@ -354,11 +354,12 @@ LSurl :: add_handler('#^tmp/(?P<filename>[^/]+)$#', 'handle_tmp_file');
* @param[in] $request LSurlRequest The request
* @param[in] $instanciate boolean Instanciate and return an object (optional, default: true)
* @param[in] $check_access callable|null Permit to specify check access method (optional, default: LSsession :: canAccess())
* @param[in] $api_mode boolean Enable API mode (optional, default: false)
*
* @retval LSobject|boolean The instanciated LSobject (or True if $instanciate=false), or False
* on error/access refused
*/
function get_LSobject_from_request($request, $instanciate=true, $check_access=null) {
function get_LSobject_from_request($request, $instanciate=true, $check_access=null, $api_mode=false) {
$LSobject = $request -> LSobject;
$dn = (isset($request -> dn)?$request -> dn:null);
@ -367,7 +368,7 @@ function get_LSobject_from_request($request, $instanciate=true, $check_access=nu
$check_access = array('LSsession', 'canAccess');
// Handle SELF redirect
if ( $LSobject == 'SELF' ) {
if ( !$api_mode && $LSobject == 'SELF' ) {
$LSobject = LSsession :: getLSuserObject() -> getType();
$dn = LSsession :: getLSuserObjectDn();
LSurl :: redirect("object/$LSobject/".urlencode($dn));
@ -377,12 +378,18 @@ function get_LSobject_from_request($request, $instanciate=true, $check_access=nu
if ($dn) {
if (!call_user_func($check_access, $LSobject, $dn)) {
LSerror :: addErrorCode('LSsession_11');
if ($api_mode)
LSsession :: displayAjaxReturn();
else
LSsession :: displayTemplate();
return false;
}
}
else if (!LSsession :: in_menu($LSobject) && !call_user_func($check_access, $LSobject)) {
LSerror :: addErrorCode('LSsession_11');
if ($api_mode)
LSsession :: displayAjaxReturn();
else
LSsession :: displayTemplate();
return false;
}
@ -1469,3 +1476,429 @@ function handle_old_addon_view($request) {
LSurl :: redirect();
}
LSurl :: add_handler('#^addon_view\.php#', 'handle_old_addon_view', false);
/*
* API
*/
/*
* LSobject API view helper to retreive LSobject from request
*
* Just a wrapper on get_LSobject_from_request() helper function to
* correctly set parameters for API context.
*
* See get_LSobject_from_request() for details.
*
* @param[in] $request LSurlRequest The request
* @param[in] $instanciate boolean Instanciate and return an object (optional, default: true)
* @param[in] $check_access callable|null Permit to specify check access method (optional, default: see get_LSobject_from_request())
*
* @retval LSobject|boolean The instanciated LSobject (or True if $instanciate=false), or False
* on error/access refused
*/
function get_LSobject_from_API_request($request, $instanciate=true, $check_access=null) {
return get_LSobject_from_request($request, $instanciate, $check_access, true);
}
/*
* Handle API LSobject search
*
* @param[in] $request LSurlRequest The request
*
* @retval void
**/
function handle_api_LSobject_search($request) {
LSsession :: setAjaxDisplay();
$object = get_LSobject_from_API_request($request);
if (!$object)
return;
$LSobject = $object -> getType();
if (!LSsession :: loadLSclass('LSsearch')) {
LSsession :: addErrorCode('LSsession_05', 'LSsearch');
LSsession :: displayAjaxReturn();
return false;
}
// Instanciate a LSsearch
$search = new LSsearch($LSobject, 'api', null, isset($_REQUEST['reset']));
$search -> setParam('onlyAccessible', True);
if (!$search -> setParamsFromRequest()) {
LSsession :: displayAjaxReturn();
return;
}
// Run search
if (!$search -> run())
LSlog :: fatal('Fail to run search.');
$all = isset($_REQUEST['all']);
if ($all) {
$entries = $search -> listEntries();
if (!is_array($entries))
LSlog :: fatal("Fail to retreive search result");
}
else {
// Retrieve page
$page_nb = (isset($_REQUEST['page'])?(int)$_REQUEST['page']:0);
$page = $search -> getPage($page_nb);
/*
* $page = array(
* 'nb' => $page,
* 'nbPages' => 1,
* 'list' => array(),
* 'total' => $this -> total
* );
*/
// Check page
if (!is_array($page) || $page_nb > $page['nbPages'])
LSlog :: fatal("Fail to retreive page #$page_nb.");
}
// Handle JSON output
$data = array(
'success' => true,
'objects' => array(),
'total' => $search -> total,
);
if (!$all) {
$data['page'] = $page['nb'] + 1;
$data['nbPages'] = $page['nbPages'];
}
foreach(($all?$entries:$page['list']) as $obj) {
$data['objects'][$obj -> dn] = array(
'name' => $obj -> displayName,
);
if ($search -> displaySubDn)
$data['objects'][$obj -> dn][$search -> label_level] = $obj -> subDn;
if ($search -> extraDisplayedColumns) {
foreach ($search -> visibleExtraDisplayedColumns as $cid => $conf) {
$data['objects'][$obj -> dn][$conf['label']] = $obj -> $cid;
}
}
foreach ($search -> attributes as $attr) {
if (LSsession :: canAccess($LSobject, $obj -> dn, 'r', $attr))
$data['objects'][$obj -> dn][$attr] = $obj -> $attr;
}
}
LSsession :: displayAjaxReturn($data);
}
LSurl :: add_handler('#^api/1.0/object/(?P<LSobject>[^/]+)/?$#', 'handle_api_LSobject_search', true, false, true);
/*
* Handle API LSobject create request
*
* @param[in] $request LSurlRequest The request
*
* @retval void
**/
function handle_api_LSobject_create($request) {
LSsession :: setAjaxDisplay();
$object = get_LSobject_from_API_request(
$request,
true, // instanciate object
array('LSsession', 'canCreate') // Check access method
);
if (!$object)
return;
$data = array('success' => false);
$LSobject = $object -> getType();
$form = $object -> getForm('create', null, true); // Create form in API mode
if (isset($_REQUEST['dataEntryForm'])) {
$form -> applyDataEntryForm((string)$_REQUEST['dataEntryForm']);
}
$form -> setSubmited();
if ($form->validate(true)) {
// Data update for LDAP object
if ($object -> updateData('create')) {
$data['success'] = true;
$data['type'] = $object -> getType();
$data['dn'] = $object -> getDn();
$data['name'] = $object -> getDisplayName();
LSsession :: addInfo(_("Object has been added."));
}
else {
$data['fields_errors'] = $form -> getErrors();
}
}
else if ($form -> definedError()) {
$data['fields_errors'] = $form -> getErrors();
}
LSsession :: displayAjaxReturn($data);
}
LSurl :: add_handler('#^api/1.0/object/(?P<LSobject>[^/]+)/create/?$#', 'handle_api_LSobject_create', true, false, true);
/*
* Handle API LSobject show request
*
* @param[in] $request LSurlRequest The request
*
* @retval void
**/
function handle_api_LSobject_show($request) {
LSsession :: setAjaxDisplay();
$object = get_LSobject_from_API_request($request);
if (!$object)
return;
$data = array(
'success' => true,
'dn' => $object -> getDn(),
'type' => $object -> getType(),
'name' => $object -> getDisplayName(),
'attributes' => array(),
'relations' => array(),
);
$view = $object -> getView();
foreach($view -> elements as $element) {
$data['attributes'][$element -> name] = $element -> getApiValue();
}
if (LSsession :: loadLSclass('LSrelation')) {
foreach ($object -> getConfig('LSrelation', array(), 'array') as $rel_name => $rel_conf) {
$data['relations'][$rel_name] = array();
$relation = new LSrelation($object, $rel_name);
$list = $relation -> listRelatedObjects();
if (is_array($list)) {
foreach($list as $o) {
$data['relations'][$rel_name][$o -> getDn()] = $o -> getDisplayName(NULL,true);
}
}
else {
LSlog :: error("Fail to load related objects.");
}
}
}
LSsession :: displayAjaxReturn($data);
}
LSurl :: add_handler('#^api/1.0/object/(?P<LSobject>[^/]+)/?(?P<dn>[^/]+)/?$#', 'handle_api_LSobject_show', true, false, true);
/*
* Handle API LSobject modify request
*
* @param[in] $request LSurlRequest The request
*
* @retval void
**/
function handle_api_LSobject_modify($request) {
LSsession :: setAjaxDisplay();
$object = get_LSobject_from_API_request(
$request,
true, // instanciate object
array('LSsession', 'canEdit') // Check access method
);
if (!$object)
return;
$data = array(
'dn' => $object -> getDn(),
'type' => $object -> getType(),
'name' => $object -> getDisplayName(),
'success' => false,
);
$form = $object -> getForm('modify', null, true); // Create form in API mode
$form -> setSubmited();
if ($form->validate(true)) {
// Update LDAP object data
if ($object -> updateData('modify')) {
// Update successful
if (LSerror::errorsDefined()) {
LSsession :: addInfo(_("The object has been partially modified."));
}
else {
LSsession :: addInfo(_("The object has been modified successfully."));
$data['success'] = true;
}
}
elseif ($form -> definedError()) {
$data['fields_errors'] = $form -> getErrors();
}
}
else if ($form -> definedError()) {
$data['fields_errors'] = $form -> getErrors();
}
LSsession :: displayAjaxReturn($data);
}
LSurl :: add_handler('#^api/1.0/object/(?P<LSobject>[^/]+)/(?P<dn>[^/]+)/modify/?$#', 'handle_api_LSobject_modify', true, false, true);
/*
* Handle API LSobject remove request
*
* @param[in] $request LSurlRequest The request
*
* @retval void
**/
function handle_api_LSobject_remove($request) {
LSsession :: setAjaxDisplay();
$object = get_LSobject_from_API_request(
$request,
true, // instanciate object
array('LSsession', 'canRemove') // Check access method
);
if (!$object)
return;
$data = array(
'dn' => $object -> getDn(),
'type' => $object -> getType(),
'name' => $object -> getDisplayName(),
'success' => false,
);
// Remove object (if validated)
if ($object -> remove()) {
LSsession :: addInfo(getFData(_('%{objectname} has been successfully deleted.'), $data['name']));
$data['success'] = true;
}
else {
LSerror :: addErrorCode('LSldapObject_15', $objectname);
}
LSsession :: displayAjaxReturn($data);
}
LSurl :: add_handler('#^api/1.0/object/(?P<LSobject>[^/]+)/(?P<dn>[^/]+)/remove/?$#', 'handle_api_LSobject_remove', true, false, true);
/*
* Handle API LSobject relation request
*
* @param[in] $request LSurlRequest The request
*
* @retval void
**/
function handle_api_LSobject_relation($request) {
LSsession :: setAjaxDisplay();
$object = get_LSobject_from_API_request(
$request,
true, // instanciate object
);
if (!$object)
return;
$LSobject = $object -> getType();
// Handle relation URL parameter
$relationName = $request -> relation;
if (!is_array($object -> getConfig("LSrelation.$relationName"))) {
LSlog :: log_error("LSobject $LSobject have no relation '$relationName'.");
LSsession :: displayAjaxReturn();
return false;
}
// Check user access to this relation
if (
(isset($_REQUEST['add']) || isset($_REQUEST['remove'])) &&
!LSsession :: relationCanEdit($object -> dn, $LSobject, $relationName)
) {
LSerror :: addErrorCode('LSsession_11');
LSsession :: displayAjaxReturn();
return false;
}
// Load LSrelation PHP class (with warning)
if (!LSsession :: loadLSclass('LSrelation', null, true)) {
LSsession :: displayAjaxReturn();
return false;
}
$relation = new LSrelation($object, $relationName);
$data = array(
'dn' => $object -> getDn(),
'type' => $object -> getType(),
'name' => $object -> getDisplayName(),
'relation' => $relationName,
'success' => false,
);
$warnings = array();
// List current related objects
$list = $relation -> listRelatedObjects();
$listDns = array();
if (is_array($list)) {
foreach($list as $o) {
$listDns[] = $o -> getDn();
}
}
LSlog :: debug("Current related object(s): ".varDump($listDns));
// Keep a copy of initial related objects list
$initialListDns = $listDns;
// Handle add
$relatedLSobject = $object -> getConfig("LSrelation.$relationName.LSobject");
$search = new LSsearch(
$relatedLSobject,
"LSrelation.api.$LSobject.$relationName",
array(
'scope' => 'base',
)
);
if (isset($_REQUEST['add'])) {
foreach (ensureIsArray($_REQUEST['add']) as $dn) {
$dn = urldecode($dn);
// Check if DN is already in relation
if (in_array($dn, $listDns)) {
LSlog :: debug("LSobject $relatedLSobject $dn is already in relation with ".$object -> getDn().".");
continue;
}
// Check DN refer to a related object
$search -> setParam('basedn', $dn);
$search -> run(false);
$result = $search -> listObjectsDn();
if (!is_array($result) || count($result) != 1) {
$warnings[] = "No $relatedLSobject found for DN $dn";
}
$listDns[] = $dn;
}
}
if (isset($_REQUEST['remove'])) {
// Handle remove
foreach (ensureIsArray($_REQUEST['remove']) as $dn) {
$dn = urldecode($dn);
$found = false;
while(true) {
$key = array_search($dn, $listDns);
if ($key === false) break;
$found = true;
unset($listDns[$key]);
}
if (!$found)
LSlog :: debug("LSobject $relatedLSobject $dn is not in relation with ".$object -> getDn().".");
}
}
// Add new related objects list in result
$data['relatedObjects'] = array_values($listDns);
if ($warnings) {
LSerror :: addErrorCode(false, "Some problems detected on requested changes.");
$data['warnings'] = $warnings;
}
else if ($initialListDns == $listDns) {
LSsession :: addInfo('No changes done.');
$data['success'] = true;
}
else {
LSlog :: debug("New related object(s) list: ".varDump($listDns));
if ($relation -> updateRelations($listDns)) {
LSsession :: addInfo('Objects in relation updated.');
$data['success'] = true;
}
else {
LSerror :: addErrorCode(false, "Fail to update objects in relation");
}
}
LSsession :: displayAjaxReturn($data);
}
LSurl :: add_handler('#^api/1.0/object/(?P<LSobject>[^/]+)/(?P<dn>[^/]+)/relation/(?P<relation>[^/]+)/?$#', 'handle_api_LSobject_relation', true, false, true);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff