web-dev-qa-db-fra.com

Écrire un fichier avec des autorisations spécifiques dans Python

J'essaie de créer un fichier uniquement lisible et inscriptible par l'utilisateur (0600).

Est-ce la seule façon de le faire en utilisant os.open() comme suit?

import os
fd = os.open('/path/to/file', os.O_WRONLY, 0o600)
myFileObject = os.fdopen(fd)
myFileObject.write(...)
myFileObject.close()

Idéalement, j'aimerais pouvoir utiliser le mot clé with pour pouvoir fermer automatiquement l'objet. Y a-t-il une meilleure façon de faire ce que je fais ci-dessus?

51
lfaraone

Quel est le problème? file.close() fermera le fichier même s'il était ouvert avec os.open().

with os.fdopen(os.open('/path/to/file', os.O_WRONLY | os.O_CREAT, 0o600), 'w') as handle:
  handle.write(...)
36
vartec

Cette réponse répond à plusieurs préoccupations avec réponse de vartec , en particulier la préoccupation umask.

import os
import stat

# Define file params
fname = '/tmp/myfile'
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL  # Refer to "man 2 open".
mode = stat.S_IRUSR | stat.S_IWUSR  # This is 0o600.
umask = 0o777 ^ mode  # Prevents always downgrading umask to 0.

# For security, remove file with potentially elevated mode
try:
    os.remove(fname)
except OSError:
    pass

# Open file descriptor
umask_original = os.umask(umask)
try:
    fdesc = os.open(fname, flags, mode)
finally:
    os.umask(umask_original)

# Open file handle and write to file
with os.fdopen(fdesc, 'w') as fout:
    fout.write('something\n')

Si le mode souhaité est 0600, il peut être plus clairement spécifié comme le nombre octal 0o600. Encore mieux, utilisez simplement le module stat.

Même si l'ancien fichier est d'abord supprimé, une condition de concurrence est toujours possible. Comprenant os.O_EXCL avec os.O_CREAT dans les indicateurs empêchera la création du fichier s'il existe en raison d'une condition de concurrence critique. Il s'agit d'une mesure de sécurité secondaire nécessaire pour empêcher l'ouverture d'un fichier qui peut déjà exister avec un mode potentiellement élevé. Dans Python 3, FileExistsError avec [Errno 17] est levé si le fichier existe.

A défaut de définir d'abord le umask sur 0 ou pour 0o777 ^ mode peut entraîner un mode (autorisation) incorrect défini par os.open. En effet, la valeur par défaut umask n'est généralement pas 0, et il sera appliqué au mode spécifié. Par exemple, si mon umask d'origine est 2 c'est à dire. 0o002, et mon mode spécifié est 0o222, si je n'arrive pas à définir d'abord le umask, le fichier résultant peut avoir à la place un mode de 0o220, ce qui n'est pas ce que je voulais. Par man 2 open, le mode du fichier créé est mode & ~umask.

umask est restauré à sa valeur d'origine dès que possible. Cette obtention et ce paramètre ne sont pas thread-safe, et un threading.Lock doit être utilisé dans une application multithread.

Pour plus d'informations sur umask, reportez-vous à ce fil .

28
Acumenus

mise à jour Mes amis, bien que je vous remercie pour les votes positifs ici, je dois moi-même m'opposer à ma solution initialement proposée ci-dessous. La raison est de faire les choses de cette façon, il y aura un certain temps, aussi petit soit-il, où le fichier existe et n'a pas les autorisations appropriées en place - cela laisse de larges voies d'attaque ouvertes, et même un comportement buggé.
Bien sûr, la création du fichier avec les autorisations appropriées est la voie à suivre - contre l'exactitude de cela, utiliser with de Python n'est qu'un bonbon.

Alors s'il vous plaît, prenez cette réponse comme un exemple de "ce qu'il ne faut pas faire";

message d'origine

Vous pouvez utiliser os.chmod au lieu:

>>> import os
>>> name = "eek.txt"
>>> with open(name, "wt") as myfile:
...   os.chmod(name, 0o600)
...   myfile.write("eeek")
...
>>> os.system("ls -lh " + name)
-rw------- 1 gwidion gwidion 4 2011-04-11 13:47 eek.txt
0
>>>

(Notez que la façon d'utiliser les octales dans Python est en étant explicite - en le préfixant avec "0o" comme dans "0o600 ". En Python 2.x, cela fonctionnerait en écrivant simplement 0600 - mais c'est à la fois trompeur et déconseillé.)

Cependant, si votre sécurité est critique, vous devriez probablement recourir à sa création avec os.open, comme vous le faites et utilisez os.fdopen pour récupérer un Python objet fichier à partir du descripteur de fichier renvoyé par os.open.

12
jsbueno

La question consiste à définir les autorisations pour être sûr que le fichier ne sera pas lisible dans le monde ( uniquement en lecture/écriture pour l'utilisateur actuel ).

Malheureusement, seul, le code:

fd = os.open('/path/to/file', os.O_WRONLY, 0o600)

ne garantit pas que les autorisations seront refusées au monde. Il essaie de définir r/w pour l'utilisateur actuel (à condition que umask le permette), c'est tout!

Sur deux systèmes de test très différents, ce code crée un fichier avec - rw-r - r - avec mon umask par défaut, et - rw-rw-rw - avec umask (0) qui n'est certainement pas ce qui est souhaité (et qui pose un grave risque pour la sécurité).

Si vous voulez vous assurer que le fichier n'a pas de bits définis pour le groupe et le monde, vous devez d'abord masquer ces bits (rappelez-vous - umask est déni des autorisations):

os.umask(0o177)

En outre, pour être sûr à 100% que le fichier n'existe pas déjà avec des autorisations différentes, vous devez d'abord le modifier/le supprimer (la suppression est plus sûre, car vous n'avez peut-être pas d'autorisations d'écriture dans le répertoire cible - et si vous avez des problèmes de sécurité , vous ne voulez pas écrire de fichier là où vous n'êtes pas autorisé à le faire!), sinon vous pourriez avoir un problème de sécurité si un pirate a créé le fichier avant vous avec des autorisations r/w dans le monde entier en prévision de votre déménagement. Dans ce cas, os.open ouvrira le fichier sans définir ses autorisations du tout et vous vous retrouvez avec un fichier secret mondial r/w ...

Il vous faut donc:

import os
if os.path.isfile(file):
    os.remove(file)
original_umask = os.umask(0o177)  # 0o777 ^ 0o600
try:
    handle = os.fdopen(os.open(file, os.O_WRONLY | os.O_CREAT, 0o600), 'w')
finally:
    os.umask(original_umask)

C'est le moyen sûr de garantir la création d'un fichier -rw ------- quels que soient votre environnement et votre configuration. Et bien sûr, vous pouvez attraper et gérer les IOErrors au besoin. Si vous n'avez pas d'autorisations d'écriture dans le répertoire cible, vous ne devriez pas pouvoir créer le fichier et s'il existait déjà, la suppression échouera.

3
jytou

Je voudrais suggérer une modification de l'excellente réponse d'A-B-B qui sépare un peu plus clairement les préoccupations. Le principal avantage est que vous pouvez gérer les exceptions qui se produisent lors de l'ouverture du descripteur de fichier séparément des autres problèmes lors de l'écriture réelle dans le fichier.

L'extérieur try ... finally block s'occupe de gérer les problèmes d'autorisation et de umask lors de l'ouverture du descripteur de fichier. Le bloc with interne traite les exceptions possibles lors de l'utilisation de l'objet fichier Python (car c'était le souhait de l'OP):

try:
    oldumask = os.umask(0)
    fdesc = os.open(outfname, os.O_WRONLY | os.O_CREAT, 0o600)
    with os.fdopen(fdesc, "w") as outf:
        # ...write to outf, closes on success or on exceptions automatically...
except IOError, ... :
    # ...handle possible os.open() errors here...
finally:
    os.umask(oldumask)

Si vous souhaitez ajouter au fichier au lieu d'écrire, le descripteur de fichier doit être ouvert comme suit:

fdesc = os.open(outfname, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)

et l'objet fichier comme ceci:

with os.fdopen(fdesc, "a") as outf:

Bien sûr, toutes les autres combinaisons habituelles sont possibles.

1
Laryx Decidua

Je ferais différemment.

from contextlib import contextmanager

@contextmanager
def umask_helper(desired_umask):
    """ A little helper to safely set and restore umask(2). """
    try:
        prev_umask = os.umask(desired_umask)
        yield
    finally:
        os.umask(prev_umask)

# ---------------------------------- […] ---------------------------------- #

        […]

        with umask_helper(0o077):
            os.mkdir(os.path.dirname(MY_FILE))
            with open(MY_FILE, 'wt') as f:
                […]

Le code de manipulation de fichiers a tendance à être déjà try-except- lourd; l'aggraver avec le finally d'os.umask ne va plus vous faire plaisir. Pendant ce temps, rouler votre propre gestionnaire de contexte est aussi simple que cela, et entraîne une imbrication d'indentation quelque peu plus nette.

0
ulidtko