[cl-openid-devel] Re: provider API

Anton Vodonosov avodonosov at yandex.ru
Thu Jul 31 22:49:41 UTC 2008


on Thursday, July 31, 2008, 4:25:18 PM Maciek wrote:

> Anton Vodonosov <avodonosov at yandex.ru> writes:

>> What I mean, the user must create something like following code,
>> based on the pattern we will provide in the example:
>>
>> (define-easy-handler (opendid-provider-endpoint
>>                       "/openid-provider-endpoint")
>>     ((mode :real-name "openid.mode")
>>      (id :real-name "openid.identity"))
>>   (let ((params (append (hunchentoot:post-parameters)
>>                         (hunchentoot:get-parameters))))
>>     (cond ((string= mode "associate")
>>            (cl-openid:create-association *provider* 
>>                                          params 
>>                                          hunchentoot:ssl-p))
>>           ((string= mode "checkid_immediate")
>>            ;; here user's code cames, for example:
>>            (if (and (string= (session-value 'cur-user)
>>                              id)
>>                     ;; BTW, it looks like we completelly 
>>                     ;; missed return_to verification 
>>                     ;; based on realm; see 9.2.1
>>                     ;; we must provide some function(s) for this
>>                     (cl-open-id:realm-is-good ...)) 
>>                (hunchentoot:redirect (positive-assertion-uri *provider* 
>>                                                              params))
>>                (hunchentoot:redirect (negative-assertion-uri *provider* 
>>                                                              params)))
>>           ((string= mode "checkid_setup")
>>              ;; here user's defined code too
>>              ... )
>>           ((string= mode "check_authentication")
>>            (check-authentication *provider* params))
>>           (t (error-response (format nil 
>>                                      "Unknown openid.mode ~S" 
>>                                      mode))))))
>>
>> I.e. no callbacks, user does not plug into our code, but wraps our
>> code into his handler. I suspect this would be simpler for him.
>> He must dispatch all the modes to predefined functions, except
>> for "checkid_setup" and "checkid_immediate" that he handles
>> himself.

> I don't like this.  This way, we put too much of protocol-related code
> in user's hands.  That's what higher-order functions are for; and
> protocol implementation really belongs to our library.  I see it more
> like this:

> (defvar *op*
>   (make-instance 'openid-provider
>                  ;; true -> id_res, false -> setup_needed, error raised -> error
>                  :immediate-callback #'(lambda (provider message)
>                                          (string= (session-value 'cur-user)
>                                                   (claimed-id message)))

>                  ;; Do own authentication (e.g. redirect to login page), finally redirect
>                  :setup-callback #'(lambda (provider message)
>                                      ...)))

> ;; Or:
> (defclass my-op (openid-provider) ()
>   ())

> (defmethod checkid-immediate ((op my-op) message)
>   (string= (session-value 'cur-user) (claimed-id message)))

> (defmethod checkid-setup ((op my-op) message)
>   ...)

> (defvar *op*
>   (make-instance 'my-op ...))

> ; Maybe instead of subclassing, the EQL-specialized methods would be
> ; good enough?

> ;; Finally:
> (define-easy-handler (openid-provider-endpoint "/openid-provider-endpoint") ()
>   (multiple-value-bind (body status redirect-uri)
>       (cl-openid:handle-provider-request *op* (append
> (get-parameters) (post-parameters)))
>     (when redirect-uri
>       (hunchentoot:redirect redirect-uri))
>     (when status
>       (setf (hunchentoot:return-code status)))
>     body))

> It's way simpler and less fragile.  There is no protocol
> implementation mixed into HTTP handler.

Maybe, I can not say for sure now.

Initially I too thought about a single entry point that user must call.
But after the entry point started looking requiring complex
parameterization, I started considering the variant I provided above.

It is like in TCP/IP. bind, then listen, then accept is a commonly
known pattern, but sockets API (at least BSD sockets, and others
modeled after that) does not provide a single function for this.
User must understand what is IP addres, port, bind, listen, accept
and implement the pattern.

Perhaps another advantage of more protocol-oriented API is that
user must learn only one protocol - OpenID, instead of learning
two: OpenID and a 'protocol' of extending our API. With explicit
OpenID protocol flow in his code, user feel understanding and
controlling over what is happening, which is essential characteristic
of good design (of any technical system, be it API, UI or whatever).

Anyway, I suggest to start coding the provider example in a
separate package. The example must handle all the relevant use cases:

 - immediate requests,
 - checkid_setup requests with login,
 - handle realms. For example, when you login to livejournal by
   a blogger account, blogger first time asks you:
      Tell http://www.livejournal.com/ that you own
      http://yournick.blogger.com ?
        - Yes, just once.
        - Yes, always.
        - No.
   If you answer "Yes, always", next time it will not ask.
 - invalidate associations (btw, another missed point
   in the current provider, see the section 10 of the spec).
 - maybe something else I missed.

When you polish the example code, you will have the API. What
is good for you, will be good for others.

Remaining refactoring may be done during this process.

Regards,
-Anton





More information about the cl-openid-devel mailing list