Créez un driver FUSE pour Google Drive

Magazine
Marque
GNU/Linux Magazine
HS n°
Numéro
90
Mois de parution
mai 2017
Spécialité(s)


Résumé

Google aime beaucoup le Python et le fait savoir. Avant même de vouloir compter dans ses rangs le créateur du langage, il mettait à la disponibilité des programmeurs Python une grande partie de leurs API. Et Google Drive n'échappe pas à la règle.


Body

Derrière l'appellation cloud se cache souvent beaucoup de concepts, qui à mon sens n’ont rien à y faire. À l'inverse, les services proposant une gestion de vos données multimédias disponibles partout où une connexion internet est disponible, assurant par là même une sauvegarde et redondance de vos données, constituent un réel service de cloud. Google Drive est le service de stockage de données « offert » par Google dans la limite d'un stockage de 15 Gio. Pour plus d'espace, il faudra, comme la plupart des concurrents, utiliser la carte bancaire. Mais comment faire pour intégrer un partage Google Drive à Linux ? Python bien sûr. Nous aborderons les dessous de l'API Python, offerte par Google, avant d'en explorer le fonctionnement. Ensuite, nous verrons comment l'intégrer de façon transparente dans Linux via l'API FUSE et son binding Python.

1. REST

L'API Google Drive est une API de type REST (REpresentational State Transfer), elle respecte donc une convention permettant l'interrogation du service au travers du protocole HTTP et, dans notre cas, HTTP2. De nombreuses applications Google reposent sur cette convention.

Mais un problème subsiste quand on implémente une API respectant une telle architecture, que l'on définit souvent sous le terme de RESTFull : c'est l'aspect « sans état ». Ceci implique que chaque requête est complètement décorrélée, et donc exclut automatiquement tout moyen d'authentification et donc d'identification des ressources auxquelles un tiers peut avoir droit. Le protocole OAuth2 répond à cette problématique.

2. OAuth2

L'API permettant de communiquer avec Google Drive repose, comme l'intégralité des API Google, sur le protocole OAuth. OAuth et son successeur OAuth2 ne sont pas à proprement parler, des protocoles d'authentification, mais il faut plutôt les voir comme des protocoles de délégation d'identité.

Une application possède de nombreuses ressources qu'il peut être intéressant de rendre disponibles, du moins en partie, à d'autres applications. C'est bien cette problématique que tente de résoudre le protocole OAuth2 (car OAuth 1 n'est plus recommandé). Il fut initié par Blaine Cook, alors ingénieur chez Twitter. Il était responsable de l'implémentation de OpenID, un service de mutualisation d'authentication afin d'accéder avec un compte unique à plusieurs applications web. Durant cette implémentation, il s'est rendu compte qu'il n'existait aucun standard permettent de déléguer une partie des ressources d'une application à une autre. C'est pour cela qu'il a eu l'idée d'initier le protocole OAuth. Ce projet fût très vite soutenu par Google qui y voyait un moyen de développer la communauté des développeurs autour de leurs applications et ainsi inciter les utilisateurs à utiliser indirectement les ressources mises à disposition par Google.

OAuth est donc basé sur de la délégation d'autorisations. On voit donc deux notions émerger :

  • une application de type fournisseur offrant un service via une API ;
  • une application de type consommatrice utilisant le service de la précédente.

Concrètement, l'utilisateur de l'application de type fournisseur va définir un périmètre fonctionnel qu'il va ensuite déléguer à l'application de type consommatrice au travers d'un token. Ce dernier sera donc présenté à chaque appel de l'API afin de l'identifier.

3. API Python

L'API Google repose donc sur un protocole ouvert, OAuth2, et le protocole HTTP2. Cette dernière est très bien documentée et nous allons décrire les étapes afin de créer une application permettant de s'interfacer avec le compte Google Drive de n'importe quel client.

3.1 Génération du token OAuth2

Dans un premier temps, il faut créer notre token nous permettant de nous authentifier auprès du service. Pour ce faire, il nous faut un compte Google et ensuite nous connecter au service de développeur à l'adresse suivante https://console.developers.google.com (voir figure 1). Ensuite, il faut créer une nouvelle application que nous allons nommer Test Linux Magazine.

googledrive_figure_01

 

Fig. 1 : Console de développeur Google. Liste de toutes les APIs Google disponibles.

Il faut donc activer Google Drive. Ensuite, il faut créer notre token OAuth2 en sélectionnant l'onglet identifiant puis ID client Oauth(voir figure 2).

googledrive_figure_02

 

Fig. 2 : Liste des identifiants disponibles pour une application.

Ensuite nous allons télécharger ce token au format JSON pour la suite des opérations et que nous allons sauvegarder sous oauth_token.json.

3.2 Python

Notre code Python va donc se découper en une partie d'authentification basée sur la bibliothèque OAuth2 de Python, et enfin une partie plus spécifique utilisant un wrapper REST pour le Google Drive API.

Nous allons donc commencer par installer les dépendances nécessaires pour notre petite application.

# pip install httplib2 google-api-python-client

3.2.1 Authentification

L'authentification de l'application repose entièrement sur le protocole OAuth2 :

from oauth2client import client

from oauth2client import tools

from oauth2client.file import Storage

 

def get_credentials():

    home_dir = os.path.expanduser('~')

    credential_dir = os.path.join(home_dir, '.credentials')

    if not os.path.exists(credential_dir):

        os.makedirs(credential_dir)

    credential_path = os.path.join(credential_dir,

                                   'test_linux_magazine.json')

 

    store = Storage(credential_path)

    credentials = store.get()

    if not credentials or credentials.invalid:

        flow = client.flow_from_clientsecrets('oauth_token.json', 'https://www.googleapis.com/auth/drive')

        flow.user_agent = 'Test Linux Magazine'

        if flags:

            credentials = tools.run_flow(flow, store, flags)

        else:

            credentials = tools.run(flow, store)

    return credentials

Rien de bien extraordinaire dans ce code où toute la complexité est cachée par l'API. Ici, nous optimisons simplement les appels et autorisations que nous sauvegardons dans le répertoire caché, sous le répertoire root de l'utilisateur, .credentials.

Si nous exécutons cette fonction, nous verrons que notre navigateur sera automatiquement lancé sur une page d'identification Google. Après avoir rentré vos identifiants, une nouvelle page d'autorisation sera affichée, permettant donc de déléguer une partie de Google Drive à votre nouvelle application (voir figure 3).

googledrive_figure_03

 

Fig. 3 : Fenêtre d'autorisation OAuth2 pour Google Drive.

3.2.2 REST

Nous allons donc explorer les fonctionnalités que nous offre l'API REST de Google Drive. Pour cela, Google nous propose un wrapper autour des requêtes et surtout des mécanismes nous permettant de manipuler simplement les résultats en Python.

Par exemple, il est simple de lister l'ensemble des fichiers disponibles pour un utilisateur :

from apiclient import discovery

 

credentials = get_credentials()

http = credentials.authorize(httplib2.Http())

service = discovery.build('drive', 'v3', http=http)

 

results = service.files().list(fields="nextPageToken, files(id, name)").execute()

items = results.get('files', [])

L'objet items contient l'ensemble des informations de vos fichiers présents dans votre Drive.

Il est bien sûr possible de télécharger vos fichiers :

from apiclient import discovery, http

 

file_id = '0BwwA4oUTeiV1UVNwOHItT0xfa2M'

request = service.files().get_media(fileId=file_id)

fh = io.BytesIO()

downloader = http.MediaIoBaseDownload(fh, request)

done = False

while done is False:

    status, done = downloader.next_chunk()

    print("Download %d%%." % int(status.progress() * 100))

Il est aussi possible de créer un répertoire via l'API :

file_metadata = {

  'name' : 'Images',

  'mimeType' : 'application/vnd.google-apps.folder'

}

file = service.files().create(body=file_metadata,

                                    fields='id').execute()

print('Folder ID: %s' % file.get('id'))

Ici nous nous sommes attardés sur des aspects de consultation, mais il est tout aussi possible d'uploader des fichiers. Mais bien sûr ceci requiert un scope différent pour l'application.

Cette API m'a donc donné une idée. Et si nous pouvions interagir avec nos données Google Drive au travers d'un explorateur de fichiers ? Nous allons donc implémenter un driver FUSE simple pour Google Drive écrit entièrement en Python.

4. FUSE

FUSE signifie Filesystem in UserSpacE. Concrètement, FUSE est une technique qui va nous permettre d'émuler un système de fichiers depuis l'espace utilisateur, et donc sans droits root. FUSE se décompose en une partie driver relayant les appels à une bibliothèque, libfuse, en espace utilisateur.

Il existe des bindings pour presque tous les langages et bien sûr Python n'échappe pas à la règle. Il existe de nombreux bindingset ici nous avons fait le choix de fusepy.

# pip install fusepy

Nous allons ensuite reprendre le code source que nous avons testé précédemment pour initier notre « driver » en userspace.

Pour cela, nous allons implémenter une classe simple qui va permettre de lister nos fichiers depuis le compte Google Drive de notre client, et ensuite télécharger à la volée les fichiers demandés.

Nous allons donc initier une classe GoogleDriveFS qui va implémenter la méthode init() suivante :

class GoogleDriveFS(Operations):

    def __init__(self):

        self.credentials = get_credentials()

        self.http = self.credentials.authorize(httplib2.Http())

        self.service = discovery.build('drive', 'v3', http=self.http)

        self.items = {}

        self.fh = {}

        self.next_fh = 0

Les credentials sont issus de la fonction précédemment décrite ; de plus, nous rajoutons un contexte permettant de gérer nos fichiers :

  • items pour le résultat des requêtes REST ;
  • fh qui sera tous les fichiers ouverts que nous stockerons bêtement en mémoire ;
  • next_fh est un compteur de descripteur de fichiers.

Nous allons ensuite implémenter l'interface readdir appelée pour lister le contenu d'un répertoire. Dans cet exemple, nous ne gérons pas les répertoires Google Drive ; tout est contenu dans la racine.

    def readdir(self, path, fh):

        results = self.service.files().list(fields="nextPageToken, files(id, name, size)").execute()

        self.items = dict([(item['name'], (item['id'], int(item.get('size') or '0'))) for item in results.get('files', [])])

        return ['.', '..'] + list(self.items.keys())

Nous retrouvons ici notre requête précédente, plus une information de taille via l'attribut size. Nous remarquons qu'il est nécessaire d'ajouter les pseudo-répertoires . et ...

Ensuite, la méthode getattr est appelée à chaque fois que des méta-informations sont demandées. Ici pour nous tout est un fichier, nous rajoutons l'information de taille.

    def getattr(self, path, fh=None):

        if path == '/':

            return dict(st_mode=(S_IFDIR | 0o755), st_nlink=2)

        if path[1:] not in self.items:

            raise FuseOSError(EROFS)

Enfin, nous allons implémenter les méthodes de lecture de fichiers. La fonction open est appelée pour l'ouverture d'un fichier puis la fonction read de façon répétée pour lire l'intégralité d'un fichier.

    def open(self, path, flags):

        if path[1:] not in self.items:

            raise FuseOSError(EROFS)

 

        request = self.service.files().get_media(fileId=self.items[path[1:]][0])

        fh = io.BytesIO()

        downloader = http.MediaIoBaseDownload(fh, request)

        done = False

        while done is False:

            status, done = downloader.next_chunk()

 

        fh_id = self.next_fh

        self.next_fh += 1

        self.fh[fh_id] = fh

 

        return fh_id

 

    def read(self, path, size, offset, fh):

        if fh not in self.fh:

            raise FuseOSError(EROFS)

        self.fh[fh].seek(offset)

returnself.fh[fh].read(size)

Ici nous manipulons des objets au sein de la mémoire au travers d'un io.Bytes.

Il suffit de lancer notre driver et nous pouvons interagir, de façon encore un peu simpliste, avec nos informations dans le Google Drive (voir figures 4 et 5).

googledrive_figure_04

 

Fig. 4 : Intégration au sein de l'application Files sous Ubuntu.

googledrive_figure_05

 

Fig. 5 : Affichage d'une image présente depuis Google Drive.

Conclusion

100 lignes de code Python au total pour réaliser un « driver » pour Google Drive. Cela révèle la maturité de Python ainsi que son intégration dans l'écosystème Linux et réseau. Google offre une API simple pour son Drive ainsi qu'un binding Python adapté, associé aux contributeurs réalisant des bibliothèques telles que fusepy qui peuvent nous permettre d'imaginer des solutions toujours plus innovantes !



Article rédigé par

Par le(s) même(s) auteur(s)

Les derniers articles Premiums

Les derniers articles Premium

Quarkus : applications Java pour conteneurs

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Initié par Red Hat, il y a quelques années le projet Quarkus a pris son envol et en est désormais à sa troisième version majeure. Il propose un cadre d’exécution pour une application de Java radicalement différente, où son exécution ultra optimisée en fait un parfait candidat pour le déploiement sur des conteneurs tels que ceux de Docker ou Podman. Quarkus va même encore plus loin, en permettant de transformer l’application Java en un exécutable natif ! Voici une rapide introduction, par la pratique, à cet incroyable framework, qui nous offrira l’opportunité d’illustrer également sa facilité de prise en main.

De la scytale au bit quantique : l’avenir de la cryptographie

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Imaginez un monde où nos données seraient aussi insaisissables que le célèbre chat de Schrödinger : à la fois sécurisées et non sécurisées jusqu'à ce qu'un cryptographe quantique décide d’y jeter un œil. Cet article nous emmène dans les méandres de la cryptographie quantique, où la physique quantique n'est pas seulement une affaire de laboratoires, mais la clé d'un futur numérique très sécurisé. Entre principes quantiques mystérieux, défis techniques, et applications pratiques, nous allons découvrir comment cette technologie s'apprête à encoder nos données dans une dimension où même les meilleurs cryptographes n’y pourraient rien faire.

Les nouvelles menaces liées à l’intelligence artificielle

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Sommes-nous proches de la singularité technologique ? Peu probable. Même si l’intelligence artificielle a fait un bond ces dernières années (elle est étudiée depuis des dizaines d’années), nous sommes loin d’en perdre le contrôle. Et pourtant, une partie de l’utilisation de l’intelligence artificielle échappe aux analystes. Eh oui ! Comme tout système, elle est utilisée par des acteurs malveillants essayant d’en tirer profit pécuniairement. Cet article met en exergue quelques-unes des applications de l’intelligence artificielle par des acteurs malveillants et décrit succinctement comment parer à leurs attaques.

Les listes de lecture

9 article(s) - ajoutée le 01/07/2020
Vous désirez apprendre le langage Python, mais ne savez pas trop par où commencer ? Cette liste de lecture vous permettra de faire vos premiers pas en découvrant l'écosystème de Python et en écrivant de petits scripts.
11 article(s) - ajoutée le 01/07/2020
La base de tout programme effectuant une tâche un tant soit peu complexe est un algorithme, une méthode permettant de manipuler des données pour obtenir un résultat attendu. Dans cette liste, vous pourrez découvrir quelques spécimens d'algorithmes.
10 article(s) - ajoutée le 01/07/2020
À quoi bon se targuer de posséder des pétaoctets de données si l'on est incapable d'analyser ces dernières ? Cette liste vous aidera à "faire parler" vos données.
Voir les 122 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous