Après quelques jours de réflexion, j'ai pensé partager ce que j'ai découvert à ce sujet...
Les données des utilisateurs sont stockées dans l'"Open Directory", comme la version d'Apple d'OpenLDAP ou d'ActiveDirectory. Ancienne documentation ici . Deux données utilisateur contiennent des images :
- Les données d'image originales (qui remontent aux premières versions d'OSX) sont appelées
Picture
et contient un chemin d'accès à une image sur le système de fichiers. Il n'est pas nécessaire qu'il s'agisse d'une image JPEG, mais le chemin d'accès est limité à 255 caractères UTF-8.
- En
JPEGPhoto
est une image au format JPEG qui est intégrée dans l'enregistrement de l'utilisateur (contrairement à l'image qui pointe vers un fichier sur le système de fichiers). Elle semble avoir été introduite vers OSX 10.5/6.
- Si un utilisateur a à la fois
JPEGPhoto
y Picture
puis le Picture
est ignorée (au moins par les écrans de connexion et de préférences de l'utilisateur).
- En
JPEGPhoto
est stockée sous forme de données d'image JPEG codées en base64.
Vous pouvez utiliser dscl
(ligne de commande du service d'annuaire) pour interroger l'annuaire et extraire les données de l'image. Dans ce cas, les données de l'image sont présentées sous forme de données hexadécimales (c'est-à-dire que la base64 est décodée en hexadécimal) :
dscl . read /Users/alice JPEGPhoto
Vous pouvez décoder les données hexagonales dans un fichier image :
dscl . read /Users/alice JPEGPhoto | tail -1 | xxd -r -p > photo.jpg
(un moyen rapide de visualiser une image à partir de la ligne de commande de MacOS est le suivant qlmanage -p photo.jpg
)
La suppression d'images se fait également à l'aide de dscl
:
dscl . delete /Users/alice JPEGPhoto
dscl . delete /Users/alice Picture
Vous pouvez également utiliser dscl
pour charger les données :
dscl . read /Users/alice Picture /path/to/alice.jpg
C'est pratique pour le Picture
mais JPEGPhoto
a peu de chances de tenir sur la ligne de commande en tant qu'argument. À la place, vous pouvez utiliser dsimport
pour importer JPEGPhoto. Pour ce faire, vous devez créer un fichier d'importation comme indiqué ci-dessous. ici ce qui se traduit par le schéma suivant, que l'on retrouve dans d'autres réponses, articles et billets de blog :
0x0A 0x5C 0x3A 0x2C dsRecTypeStandard:Users 2 dsAttrTypeStandard:RecordName externalbinary:dsAttrTypeStandard:JPEGPhoto
alice:/path/to/alice.jpg
Ceci importe l'image au chemin donné dans le JPEGPhoto de l'enregistrement de l'utilisateur, en la stockant en interne sous forme de données binaires encodées en base64. Une autre façon de procéder consiste à transmettre directement les données codées de l'image :
0x0A 0x5C 0x3A 0x2C dsRecTypeStandard:Users 2 dsAttrTypeStandard:RecordName base64:dsAttrTypeStandard:JPEGPhoto
alice:<base64 data>
En résumé, les importer un format de fichier les quatre caractères hexadécimaux du début spécifient le format des données :
- 0x0A La fin de l'enregistrement est indiquée par une nouvelle ligne
- 0x5C Le caractère d'échappement est défini comme suit : \N- Le caractère d'échappement est défini comme suit
- 0x3A Le séparateur de champ est un :
- 0x2C Le séparateur de valeur est un , (virgule)
Ces valeurs, qui sont les seules jamais vues dans les exemples, définissent le format des données de telle sorte que chaque enregistrement est une ligne à part entière, terminée par une nouvelle ligne. L'enregistrement est composé de champs séparés par des deux points et les champs à valeurs multiples séparent les valeurs à l'aide de virgules. S'il est nécessaire d'échapper à l'un ou l'autre de ces caractères, le caractère d'échappement est \
.
Ces quatre valeurs hexagonales sont suivies du type d'enregistrement (ici, nous avons dsRecTypeStandard:Users
pour les enregistrements d'utilisateurs), le nombre de champs (2) et leur nature ( dsAttrTypeStandard:RecordName
- qui est le nom d'utilisateur - et dsAttrTypeStandard:JPEGPhoto
). Les externalbinary
Le préfixe indique dsimport
comment interpréter le champ, dans ce cas comme le chemin d'accès à un fichier image (il en existe un certain nombre, notamment base64
, utf8
y externalutf8
mais la documentation à ce sujet semble manquer).
Rappelons que le JPEGPhoto
est stocké sous forme de données encodées en base64 - dsimport
effectue toute conversion nécessaire (dans le cas de base64
tel qu'il est utilisé ci-dessus, il n'y a pas de conversion).
Vous importez le fichier d'importation préparé comme suit :
dsimport dsimport_file /Local/Default M
Encore une fois, une petite explication : /Local/Default
est le chemin d'accès à l'Open Directory et M
demande que les données d'importation soient fusionnées lorsqu'elles existent. Voir le page de manuel pour dsimport . Vous pouvez ajouter --loglevel n
où n
est comprise entre 0 et 3 ( Documentation de la ligne de commande , page 118) et de visualiser les fichiers journaux écrits sur le serveur ~/Library/Logs/ImportExport
.
Si l'on remplace une image, il est nécessaire de supprimer d'abord toute image existante (utiliser dscl
décrit ci-dessus), sinon l'image n'est pas mise à jour.
En résumé, deux exemples. D'abord, passer le chemin du fichier image dans le fichier d'importation :
#!/bin/sh
user="$1"
image="$2"
dscl . delete /Users/$user JPEGPhoto
dscl . delete /Users/$user Picture
tmp="$(mktemp)"
printf "0x0A 0x5C 0x3A 0x2C dsRecTypeStandard:Users 2 dsAttrTypeStandard:RecordName externalbinary:dsAttrTypeStandard:JPEGPhoto\n%s:%s" "$user" "$image" > "$tmp"
dsimport "$tmp" /Local/Default M
rm "$tmp"
Et, deuxièmement, l'encodage de l'image dans le fichier d'importation :
#!/bin/sh
user="$1"
image="$2"
dscl . delete /Users/$user JPEGPhoto
dscl . delete /Users/$user Picture
tmp="$(mktemp)"
printf "0x0A 0x5C 0x3A 0x2C dsRecTypeStandard:Users 2 dsAttrTypeStandard:RecordName base64:dsAttrTypeStandard:JPEGPhoto\n%s:%s" "$user" "$(base64 "$image")" > "$tmp"
dsimport "$tmp" /Local/Default M
rm "$tmp"
Utiliser comme ceci :
$ sudo add-photo username /path/to/photo.jpg
Note de bas de page
Je l'utilise dans un Ansible pour mettre à jour les avatars des utilisateurs sur plusieurs Apple Macs. Ansible est exécuté sur une machine Linux, c'est pourquoi la méthode de l'image embarquée m'intéressait car je n'avais pas besoin d'écrire l'image sur le disque de chaque Mac. Au cas où quelqu'un trouverait cela utile :
- name: User avatars
become: true
ansible.builtin.shell:
cmd: |
tmp="$(mktemp)"
printf "0x0A 0x5C 0x3A 0x2C dsRecTypeStandard:Users 2 dsAttrTypeStandard:RecordName base64:dsAttrTypeStandard:JPEGPhoto\n%s:" "{{item.name}}" > "$tmp"
cat >> "$tmp"
dscl . delete /Users/{{ item.name }} JPEGPhoto
dsimport "$tmp" /Local/Default M
rc=$?
rm "$tmp"
exit $rc
stdin: "{{lookup('file', item.avatar) | b64encode }}"
loop:
"{{ users | selectattr('avatar', 'defined') }}"
En users
transmis est une liste ayant un name
y avatar
pour chacun d'entre eux.