SE's OAuth workflow in Emacs - emacs

Progress is being made rapidly on StackMode, an Emacs client for StackExchange, and now we need to be able to make authenticated requests to the API for continued testing. (The 300-request limit is starting to limit how much testing I can do in a day.)
Disclaimer: I know very little about web development; it's one of the areas I'm working on professionally. Please excuse me if I misuse any terms and feel free to correct me in the comments. Thanks!
The StackExchange API uses OAuth 2.0 authentication. Since this is a local client application with client authorization. I have the following pieces of information provided to me by StackExchange:
Client ID
Client Secret (mustn't share, so it shouldn't be necessary in this flow)
Key
Description (not OAuth related)
OAuth Domain
Application Website (not OAuth related)
Application Icon (not OAuth related)
Stack Apps Post (not OAuth related)
with the following extra pieces of information:
Client Side Flow Is Enabled
Desktop OAuth Redirect Uri Is Enabled
In order to keep any answer both general and explicit, you can use my-client-id (etc.) for values. Actual values—those I think I'm OK to share, are available on GitHub.
I've been researching this for half the day, but I'm not very much closer to a solution than when I started. The closest I've gotten is this little snippet of code:
(require 'oauth2) ; available via GNU ELPA
(defconst stack-auth-token
(make-oauth2-token
:client-id stack-auth--client-id
:client-secret stack-auth--key))
;; this doesn't use the above, but it does open an auth page on SE
(oauth2-auth-and-store
"https://stackexchange.com/oauth/dialog"
nil nil
stack-auth--client-id
stack-auth--key
"https://stackexchange.com/oauth/login_success")
The only things I have to offer an OAuth2 request (from above) are apparently
Client ID
Key
OAuth Domain
How can I implement this flow in Elisp?
Current 'Flow'
Execute oauth2-auth-and-store with proper variables set.
Opens
Click "Approve"
Opens
with this URL
The application is successfully added
But I have no code to provide oauth2
In addition to answers, PRs are also welcome, of course.

Here's a quick example. In short, this will open the auth url in the client browser, ask the user to allow the app, and then redirect to the /oauth/login_success url as described in the docs (implicit auth).
This code prompts the user to paste the login_success URL complete, then parses and saves the access_token which can then be used for subsequent calls to the api. Two interactive function are defined: so-authenticate which performs the auth steps described above, and so-read-inbox which fetches the api data for the authenticated users inbox and dumps it to the messages buffer.
Warning, this example has no error handling!
At the very least you'll want to add checks for authentication failure, api request failures and token expiration. You can see an example api error by attempting to call so-read-inbox before calling so-authenticate.
To run, paste the following into a buffer, set the so--client-id and so--client-key variables then M-x eval-buffer.
You can then use M-x so-authenticate to authenticate and M-x so-read-inbox to dump the inbox response.
(require 'json)
(defvar so--client-id "") ; SET THIS
(defvar so--client-key "") ; AND THIS
(defvar so--auth-url "https://stackexchange.com/oauth/dialog?")
(defvar so--redirect-url "https://stackexchange.com/oauth/login_success")
(defvar so--api-inbox-url "https://api.stackexchange.com/inbox?")
(defvar so--current-token nil) ; this will get set after authentication
(defun so-authenticate ()
(interactive)
(so--open-auth))
(defun so-read-inbox()
(interactive)
(so--retrieve-inbox))
;; Open auth url in browser and call so--get-save-token.
(defun so--open-auth ()
(let ((auth-url
(concat so--auth-url (url-build-query-string
`((client_id ,so--client-id)
(scope "read_inbox")
(redirect_uri ,so--redirect-url))))))
(browse-url auth-url))
(so--get-save-token))
;; Prompt user for callback URL, extract token and save in so--current-token
(defun so--get-save-token ()
(let* ((post-auth-url-string (read-string "Enter URL from your browser: "))
(token (nth 2 (split-string post-auth-url-string "[[#=&]"))))
(setq so--current-token token)
(message "Saved token: %S" token)))
;; Make a request for our inbox data
(defun so--retrieve-inbox()
(let ((inbox-url (concat so--api-inbox-url
(url-build-query-string
`((access_token ,so--current-token) ; the token from auth
(key ,so--client-key)))))) ; your client key
(url-retrieve inbox-url 'so--retrieve-inbox-cb)))
;; Parse json response for inbox request.
;; This simply dumps the parsed data to your messages buffer.
(defun so--retrieve-inbox-cb (status)
(goto-char (point-min))
(re-search-forward "^$")
(let ((inbox-data (json-read)))
(message "inbox data: %S" inbox-data)))
Now have fun parsing the response! :)

I'll try to answer as much of this as I can. I know absolutely nothing about Lisp, but I am very familiar with the Stack Exchange API and authorization flows.
"The 300-request limit is starting to limit how much testing I can do in a day."
You can upgrade this limit to 10,000 queries/day by appending your API key to the query string of method URLs (&key=...).
"Actual values—those I think I'm OK to share, are available on GitHub."
Yup, you're safe to share those since any application shipping with those values can easily be reverse-engineered or decompiled to extract the values anyway.
"4. Opens [...] page [...] with this URL"
That is intended behavior. In your screenshot, authorization was successful and the URL's hash contains the access token. You will need this token to access certain methods, such as /inbox.
What you probably want to do looks something like this:
Continue as you have been doing until you reach the end of step #4 in your example.
Prompt the user in Emacs for the URL currently displayed. They will copy and paste it as-is.
Extract the hash (everything after the rightmost '#') and parse it as you would a query string. The access_token parameter contains the value you need.
Use the access_token and key (the API key) parameters whenever invoking protected methods.

Related

Error Using Drakma for the Bing Search API Common Lisp

I am building a program that uses Bing's search API and common lisp with the Drakma library to display some results but for some reason have an error when sending a longer length query It doesn't display any of the results at all. It works for shorter length queries. I am using a temp account for this question. I have the following code.
(defun get-rid-of-spaces (var)
(cl-ppcre:regex-replace-all " " var "%20"))
(defun print-bing (search-term)
(format nil "https://api.datamarket.azure.com/Bing/Search/v1/Web?Query=%27~a%27&Options=%27DisableLocationDetection%27&$format=json&$top=1" (get-rid-of-spaces search-term)))
(defun drakma-bing (search-term)
(drakma:http-request (print-bing search-term)
:basic-authorization
'("bob.hammerston#mailinator.com" "L2gbaj+s1/KW/+ifAa9HrP0C1/kClpF4InH48Lw8UNc")))
(defun convert-to-string (response)
(map 'string #'code-char response))
And then I call this but it only works for short search terms and I can't figure out why. This doesn't work:
(convert-to-string (drakma-bing "what is the largest man in the world"))
But this does
(convert-to-string (drakma-bing "what is"))
Any idea why?
Thanks.
Edit:
I tried encoding the print-bing function by hand instead of using that function with a longer string and it still doesn't work so there must be an error with Drakma. I tried typing the domain into the web browser by hand and it works so that is why I think the error is with Drakma.
You need to use + instead of %20 for spaces.
(defun get-rid-of-spaces (var)
(cl-ppcre:regex-replace-all " " var "+"))

Emacs View Mail: trouble using vm-imap-account-alist

I have a little problem with ViewMail. In a separate file I'm defining variables with the account information of my email accounts, something like this:
(setq secret-mail-account-list-1 "imap-ssl:imap.aaa.com:993:*:login:xxx#aaa.com:password")
;;; other email accounts are defined as well
I do this to keep my password away from being recorded by Mercurial.
Then, I do write this into vm-imap-account-alist in another file:
(setq vm-imap-account-alist
'(
(secret-mail-account-list-1 "aaamail")
;;; other email accounts are here
)
)
But, after starting ViewMail with AltXvmEnter↵ I get the following error:
vm-imap-parse-spec-to-list: Wrong type argument: sequencep, secret-mail-account-list-1
How do I set the string to a variable so it is of the correct type for vm-imap-parse-spec-to-list?
I presume you got this from the EmacsWiki page. It looks like you want to pass the value of secret-mail-account-list-1 to vm-imap-account-alist rather than the symbol itself. To do so, you need to
backquote the alist and unquote the symbol with a comma to get its value:
(setq vm-imap-account-alist
`((,secret-mail-account-list-1 "aaamail") ; notice ` and ,
;;; other email accounts are here
))
At any rate, it helps to explain your error message: it looks like vm-imap-parse-spec-to-list expects a sequence (hence sequencep) such as a string which it converts to a list. Symbols are not sequences, but the content of yours is.

Could someone show what a sample emacs .erc-auth.el file would look like?

The ERC Manual shows code for loading authentication information:
(load "~/.emacs.d/.erc-auth")
but does not show what that auth info would look like. I'd appreciate a sample file.
It doesn't have any particular format. It's just another file that can contain lisp code (you're giving it to load, after all). Taking a look at the code, I'm not really sure how you would use it to set a password for use on an irc server, but you could use it to set passwords for use with nickserv; see erc-nickserv-passwords in erc-services.el.
In your ~/.emacs.d/.ercrc.el
(load "~/.emacs.d/.erc-auth")
And something like this in ~/.emacs.d/.erc-auth
(setq erc-nick "my-id")
(setq erc-password "my-pw")

Writing an Eval Procedure in Scheme?

My problem isn't with the built-in eval procedure but how to create a simplistic version of it. Just for starters I would like to be able to take this in '(+ 1 2) and have it evaluate the expression + where the quote usually takes off the evaluation.
I have been thinking about this and found a couple things that might be useful:
Unquote: ,
(quasiquote)
(apply)
My main problem is regaining the value of + as a procedure and not a symbol. Once I get that I think I should just be able to use it with the other contents of the list.
Any tips or guidance would be much appreciated.
Firstly, if you're doing what you're doing, you can't go wrong reading at least the first chapter of the Metalinguistic Abstraction section of Structure and Interpretation of Computer Programs.
Now for a few suggestions from myself.
The usual thing to do with a symbol for a Scheme (or, indeed, any Lisp) interpreter is to look it up in some sort of "environment". If you're going to write your own eval, you will likely want to provide your own environment structures to go with it. The one thing for which you could fall back to the Scheme system you're building your eval on top of is the initial environment containing bindings for things like +, cons etc.; this can't be achieved in a 100% portable way, as far as I know, due to various Scheme systems providing different means of getting at the initial environment (including the-environment special form in MIT Scheme and interaction-environment in (Petite) Chez Scheme... and don't ask me why this is so), but the basic idea stays the same:
(define (my-eval form env)
(cond ((self-evaluating? form) form)
((symbol? form)
;; note the following calls PCS's built-in eval
(if (my-kind-of-env? env)
(my-lookup form env)
;; apparently we're dealing with an environment
;; from the underlying Scheme system, so fall back to that
;; (note we call the built-in eval here)
(eval form env)))
;; "applicative forms" follow
;; -- special forms, macro / function calls
...))
Note that you will certainly want to check whether the symbol names a special form (lambda and if are necessary -- or you could use cond in place of if -- but you're likely to want more and possibly allow for extentions to the basic set, i.e. macros). With the above skeleton eval, this would have to take place in what I called the "applicative form" handlers, but you could also handle this where you deal with symbols, or maybe put special form handlers first, followed by regular symbol lookup and function application.

AllegroServe on SBCL 1.0.28 failing with `accept invalid keyword argument: :AUTO-CLOSE`

New version of SBCL 1.0.28 running on debian breaks AllegroServe 1.2.47 on incoming connection with following error:
aserve-accept-6: 05/26/09 - 21:11:01 - accept: error 0 on accept invalid
keyword argument: :AUTO-CLOSE (valid keys are
:INPUT, :OUTPUT, :ELEMENT-TYPE, :EXTERNAL-FORMAT,
:BUFFERING, :TIMEOUT).
Portable AllegroServe page does make a mention of this problem. However, no google searches turn up anything of use for this problem.
Any ideas as to how to move forward with this problem, or alternatively, links pointing to places where this has been dealt with?
After some mucking around, I've come up with the following solution:
In my source files, after I declare my package, compile/load the appropriate modules but before I declare anything in my package, I added the following code:
(defmethod sb-bsd-sockets:socket-make-stream ((socket sb-bsd-sockets:socket)
&key input output
(element-type 'character)
(buffering :full)
(external-format :default)
timeout
(auto-close t))
"Default method for SOCKET objects. An ELEMENT-TYPE of :DEFAULT
will construct a bivalent stream. Acceptable values for BUFFERING
are :FULL, :LINE and :NONE. Streams will have no TIMEOUT
by default.
The stream for SOCKET will be cached, and a second invocation of this
method will return the same stream. This may lead to oddities if this
function is invoked with inconsistent arguments \(e.g., one might request
an input stream and get an output stream in response\)."
(let ((stream
(and (slot-boundp socket 'stream) (slot-value socket 'stream))))
(unless stream
(setf stream (sb-sys:make-fd-stream
(sb-bsd-sockets:socket-file-descriptor socket)
:name "a socket"
:dual-channel-p t
:input input
:output output
:element-type element-type
:buffering buffering
:external-format external-format
:timeout timeout
:auto-close auto-close)))
(setf (slot-value socket 'stream) stream)
(sb-ext:cancel-finalization socket)
stream))
(It's basically a lift from what is in the sb-bsd-sockets/socket.lisp with the auto-close key added to the argument list)
This way I avoid modifying or patching system files, and basically hook into the sb-bsd-sockets package directly.
So far, it seems to be working as it should. Basic testing via successive calls to (room) shows me that there's no obvious memory leaks, and the performance is as expected.
Please feel free to comment on this kludge, and if you think it might affect the stability of my system in unexpected ways.