ZEP-17 : Elaboration de l'API des membres

a marqué ce sujet comme résolu.

Il me semble que j'avais testé avec une commande curl (curl -u login:password -H "Bearer TOKEN" https://beta.zestedesavoir.com/api/membres/) mais que ça ne fonctionnait quand même pas (erreur 401 renvoyée par le serveur).

Quand je fais curl -k -v -u clementine:orange https://beta.zestedesavoir.com/api/membres/

(le -k parce que sinon on me dit que le certificat ne peut être vérifié)

dans le header qu'envois curl on trouve un : Authorization: Basic Y2xlbWVudGluZTpvcmFuZ2U=

Donc effectivement il semblerait que l'authentification http et pour oauth se marchent dessus puisque les deux utilisent le même champs !

J'ai fouillé le net et j'ai pas vraiment trouvé. la doc du module django d'authentification il n'y a pas grand chose et manifestement il n'y a pas de paramètres pour nous aider. A moins qu'on puisse passer le token ailleur que via ce champs, je ne vois pas trop…

Je pense pas. Si je comprend bien, ça c'est pour permettre que le username et password soit passé à django directement, plutot que géré par apache.

Nous notre problème est légèrement différent. Pour te l'illustrer, voici ce que fais la lib requests quand tu lui demande de passer au travers un server avec identification http :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def _basic_auth_str(username, password):
    """Returns a Basic Auth string."""

    authstr = 'Basic ' + to_native_string(
        b64encode(('%s:%s' % (username, password)).encode('latin1')).strip()
    )

    return authstr


class AuthBase(object):
    """Base class that all auth implementations derive from"""

    def __call__(self, r):
        raise NotImplementedError('Auth hooks must be callable.')


class HTTPBasicAuth(AuthBase):
    """Attaches HTTP Basic Authentication to the given Request object."""
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def __call__(self, r):
        r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
        return r

Elle met donc la demande pour se connecter dans le champ Authorization du headers. C'est la meme chose avec curl (voir mon message plus haut) : le problème est que l'authentification http utilise le champ Authorization du headers, tout comme nos demandes. Du coup le token ne peut pas arriver puisque le champs est remplacé. En réalité, que ce soit curl ou request, l'access_token n'est jamais envoyé au serveur.

De là il y a pas 36 000 solutions : il faudrait que django-oauth-toolkit autorise à envoyer le access_token ailleurs que dans ce champs !

+0 -0

J'ai bien compris le problème mais je doute de ta solution. Django-oauth-toolkit (et .htaccess) se basent tous les 2 sur le standard et non, à priori, pas de raisons à proposer une alternative.

Edit :

Y'a un problème de CORS ou c'est moi qui débloque ?

Qu'est ce que tu veux dire ?

+0 -0

Oui pardon j'ai pas été clair, désolé.

Mon soucis n'a rien à voir avec le votre :\

J'ai fait un simple test en JS et je me suis fait jeter par mon browser. J'ai pas l'impression que le header Access-Control-Allow-Origin soit renvoyé dans les appels APIs. Du coup le résultat de la requête est bloqué par mesure de sécurité.

Cf. la ZEP :

Access-Control-Allow-Credentials: true ```

Pour des informations complémentaires sur CORS, rendez-vous sur son article Wikipedia version anglaise.

PS : confirmé avec l'extension Postman de Chrome, le header n'est pas renvoyé.

+0 -0

J'ai bien compris le problème mais je doute de ta solution. Django-oauth-toolkit (et .htaccess) se basent tous les 2 sur le standard et non, à priori, pas de raisons à proposer une alternative.

Je ne propose pas de solution, je dis juste que manifestement ça ne peut pas marcher puisque les deux méthodes utilisent le même champs. On ne peut donc pas passer les deux à la fois

PS : confirmé avec l'extension Postman de Chrome, le header n'est pas renvoyé.

Javier

Je confirme aussi. Voici le contenu d'une entête en mode non connecté.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "Server": "nginx",
  "Date": "Fri, 20 Feb 2015 15:44:38 GMT",
  "Vary": "Accept-Encoding, Accept, Cookie",
  "Etag": "\"541aea432ec4a1a17502dc67852d702b7e71c81690d00bf7d5dfd2ca41d87274\"",
  "Allow": "GET, PUT, PATCH, HEAD, OPTIONS",
  "P3p": "CP=\"ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV\"",
  "Content-Encoding": "gzip",
  "Transfer-Encoding": "chunked",
  "Content-Type": "application/json",
  "Via": "1.1 SE7101L3.intra.bdf.local:8080 (Cisco-WSA/8.0.6-119)",
  "Connection": "keep-alive",
  "Proxy-Connection": "keep-alive"
}

Autre chose étrange aussi le header nous dis supporter le PATCH, mais ce n'est pas le cas.

Javier : Mon récap date un peu mais ce point n'a pas changé depuis :

  • CORS n'est pas supporté. J'ai pu trouvé une librairie simple à intégrer mais je ne suis pas assez compétent pour sa configuration.

Andr0

Kje : Nous sommes d'accord. Malheureusement, il ne semble pas y avoir de solutions.

+0 -0

SpaceFox : Non, il ne fait qu'utiliser le premier header Authorization. Si tu mets le token htaccess en premier, il te dira que tu n'es pas authentifié pour l'API. Si tu mets le token de l'API en premier, il te demandera en boucle les identifiants du htaccess.

CORS n'est pas supporté. J'ai pu trouvé une librairie simple à intégrer mais je ne suis pas assez compétent pour sa configuration.

Ouah j'étais complètement passé à côté de ça…

Ça interdit quasiment tous les clients JS ou alors ils vont devoir passer par des feintes infâmes du style jsonp (EDIT : et même avec jsonp on peut pas faire de POST je crois) . J'espère que PhoneGap n'est pas impacté (il me semble que non mais c'est vraiment à confirmer) sinon ça veut dire uniquement clients desktop + mobile natif.

J'dois reconnaître que c'est une sacré déception…

Deux questions :

  1. Y'a pas forcément besoin d'une lib pour si peu, c'est juste choper la bonne hook pour avoir la réponse juste avant qu'elle ne parte à l'utilisateur. Tous les frameworks le permettent je pense, non ?

J'ai lu quelque part finalize_reponse vous n'avez pas la main là-dessus ?

Ou Django ne fournit aucun moyen d'intercepter la réponse Http (à la manière de filtres un peu, cf. Rails/Grails et leurs beforeFilterafterFilter`), et n'injecte nulle part (dans votre code, un décorateur, …) la réponse Http (à la manière de RESTtEeasy, Spring).

  1. J'ai essayé de fouiller le topic, qu'est-ce-qui te bloque dans la config de la lib que tu as donnée ? D'après la doc il suffirait dans le cas de ZdS de mettre :
1
CORS_ORIGIN_ALLOW_ALL = True

Qui doit se contenter de mettre le header qu'on veut sur la réponse http.

1
2
3
4
5
6
7
8
9
    CORS_ALLOW_HEADERS = (
        'x-requested-with',
        'content-type',
        'accept',
        'origin',
        'authorization',
        'x-csrftoken',
        'x-data-format'
    )

J'ai repris la valeur par défaut et ajouté le X-Data-Format (header maison pour le MD/html), mais là faut vérifier qu'on n'attende pas d'autre header (non standard Http !) j'ai pas balayé entièrement la ZEP et ma mémoire flanche.

Enfin :

1
2
3
4
CORS_EXPOSE_HEADERS = (
   'etag',
   'link'
)

Ça c'est marqué dans la ZEP. Le client lit quoi dans les headers à part ça ? Y'a eu du nouveau ?

Mais ptet' t'as déjà essayé tout ça, ou ptet' que ça déconne à l'install, etc.

Mais franchement ça vaut le coup de s'y pencher parce que aucun client JS ça fait quand même un peu chier… :\

+0 -0

Kje : Nous sommes d'accord. Malheureusement, il ne semble pas y avoir de solutions.

Andr0

Est ce qu'on pourrait pas s'en sortir avec un conf nginx ? nginx va actuellement vérifier le champs "Authorization", et il ne le passera probablement pas à Django. Est ce qu'on pourrait pas faire en sorte que nginx choppe le contenu d'un champs "AuthorizationOauth" et le passe dans "Authorization" après l'autorisation ?

Parce que je viens de fouiller le code de django-oauth-toolkit et oauthlib sur lequel il s’appuie et visiblement eux ne sont pas fait pour qu'on les paramètres là dessus.

Ou Django ne fournit aucun moyen d'intercepter la réponse Http (à la manière de filtres un peu, cf. Rails/Grails et leurs beforeFilterafterFilter`), et n'injecte nulle part (dans votre code, un décorateur, …) la réponse Http (à la manière de RESTtEeasy, Spring).

Javier

Sur ce point particulier, c'est possible, ça s'appelle un middleware dans le monde Django. Et il doit bien y avoir quelque chose pour travailler sur les headers. Sinon, ça doit être simple à mettre en place.

+0 -0

Javier : Je suppose que tu es tombé sur ce message dans ta recherche sur ce sujet et comme tu peux le voir, le problème n'est pas comment le mettre en oeuvre mais comment le configurer. A l'époque, j'avais demandé de l'aide (toujours sur ce même message) mais je n'avais eu aucune réponse à ce sujet et ne voulant pas faire quelque chose que je ne maitrisais pas, j'ai laissé tombé.

En conclusion, si ce que tu m'indiques permet de configurer la bibliothèque convenablement et si tu m'informes comment je peux tester la chose en local, je peux toujours faire une PR pour qu'elle soit mergée dans la release 1.6. Faut juste être un peu rapide parce que la date de mise en production approche à grand pas.

Kje : Peut-être mais je connais peu nginx. Ce sont des questions qu'il faut poser à des sys admin. Dans tous les cas, niveau code, je ne peux rien faire pour que ça fonctionne.

+0 -0

Donc on est d'accord que c'est la config de la lib qui pose problème ? (j'ai pas bien compris la différence mise en oeuvre / configurer).

Je pense que la config que j'ai donnée permettra de gérer le CORS comme indiqué dans la ZEP.

Ce qu'il faut vérifier :

  • que la réponse HTTP de tout appel API contient bien le header Access-Control-Allow-Origin: *

  • que la réponse HTTP de tout appel API contient bien le header Access-Control-Expose-Headers: ETag, X-Data-Format

C'est pas plus compliqué que ça à tester. Le test de bout en bout en JS n'a pas plus d'intérêt que de regarder les headers. S'il déconne c'est que y'a un soucis côté client.

+0 -0

Excuse moi, je suis sur mobile ce week-end, je n'ai pas de quoi bien m'expliquer. Ce que je voulais dire par mise en oeuvre plutôt qu'une simple configuration, c'est que cela ne sert à rien de développer la chose from scratch. La librairie conseillée par Django REST Framework est faite pour et il suffit que de la configurer pour la faire fonctionner.

Du coups, je vais tenter ta configuration dimanche au soir ou lundi et je viendrais ici dire ce qu'il en est. :)

J'ai de nouvelles questions :

  • La doc list une méthode PATCH sur les membres, c'est quoi le but ?
  • On peut bannir ou mettre en lecture seule, et les enlever. Mais on a aucun moyen de savoir si un membre a une sanction d'appliqué. Elle doit manquer dans les infos sur un membre, non ?
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte