scala-news-reader
rss/atom news reader in scala
git clone https://9o.is/git/scala-news-reader.git
OauthBuilder.scala
(4643B)
1 package com.joereader.lib.api.rest.oauth
2
3 import com.joereader.config._
4
5 import dispatch._
6 import Defaults._
7 import com.ning.http.client._
8
9 import net.liftweb._
10 import common.{ Logger, Box, Full }
11 import http._
12 import util._
13 import sitemap._, Loc._
14 import json._
15
16 import java.net.URL
17
18 trait OauthBuilder extends Logger {
19
20 /* Name to identify this oauth. */
21 protected def name: String
22
23 /* oauth server's sign in request url */
24 protected def signInRequest: RequestBuilder
25
26 /* oauth server's callback request url */
27 protected def callbackRequest: RequestBuilder
28
29 /* The access token. */
30 protected def token: OauthAccessToken
31
32 /* Called when oauth is done. */
33 protected def endResponse(callback: URL): LiftResponse =
34 DoRedirectResponse(callback.toString)
35
36 /*
37 * if true, sends callback data in body encoded as
38 * application/x-www-form-urlencoded
39 */
40 protected def urlEncodedCallback = false
41
42 /* oauth app key */
43 private val key =
44 Props.get(name + ".key") ?~ s"$name app key not found"
45
46 /* oauth app secret */
47 private val secret =
48 Props.get(name + ".secret") ?~ s"$name app secret not found"
49
50 /* Optional permissions. */
51 protected def scope: List[String] = Nil
52
53 /*
54 * Full base url of our server. We need this as
55 * the redirect uri.
56 */
57 protected val baseUrl =
58 Props.get("oauth.baseurl") ?~ s"$name base url not found"
59
60 /*
61 * Full callback url of our server. We need this as
62 * the redirect uri.
63 */
64 private def callbackUrl =
65 baseUrl.map(_ + myCallback.mkString("/"))
66
67 /*
68 * First step of oauth 2.0, send user to oauth server's
69 * login screen.
70 */
71 private def signIn: Box[LiftResponse] =
72 for {
73 key <- key
74 callback <- callbackUrl
75 } yield {
76
77 def permissions =
78 for(scope <- scope)
79 yield "scope" -> scope
80
81 def req = signInRequest <<? Map(
82 "client_id" -> key,
83 "redirect_uri" -> callback,
84 "scope" -> "email",
85 "response_type" -> "code",
86 "scope" -> scope.mkString(","))
87
88 DoRedirectResponse(req.url)
89 }
90
91 /*
92 * Second step of oauth 2.0, server sends us a code
93 * and we trade that code for an access token.
94 */
95 private def callback: Box[LiftResponse] =
96 for {
97 key <- key
98 secret <- secret
99 callback <- callbackUrl
100 code <- S.param("code") ?~ "Callback code not found"
101 } yield {
102 def params = Map(
103 "client_id" -> key,
104 "redirect_uri" -> callback,
105 "client_secret" -> secret,
106 "code" -> code,
107 "grant_type" -> "authorization_code")
108
109 def req =
110 if (urlEncodedCallback) callbackRequest << params
111 else callbackRequest <<? params
112
113 Http(req OK as.String).either() match {
114 case Left(e) =>
115 error(e.getMessage)
116 endResponse
117 case Right(res) =>
118 setAccessToken(res)
119 endResponse
120 }
121
122 }
123
124 /*
125 * Sets the oauth token. Override this if access
126 * token is not in body as json.
127 */
128 protected def setAccessToken(res: String) {
129 implicit val formats = DefaultFormats
130 val value = parse(res).extract[AccessToken]
131 token(Full(value))
132 info(s"$name access token has been set to $value")
133 }
134
135 /* Called when oauth is done. */
136 protected def endResponse: LiftResponse =
137 (for {
138 clientCallback <- ClientCallback.get
139 } yield {
140 endResponse(clientCallback)
141 }) openOr {
142 RedirectWithState(Site.home.url,
143 RedirectState(() => {
144 S.notice("Callback URL not found")
145 }))
146 }
147
148 /* Our website's sign in url path. */
149 private def mySignIn =
150 "auth" :: name :: "signin" :: Nil
151
152 /* Our website's callback url path. */
153 private def myCallback =
154 "auth" :: name :: "callback" :: Nil
155
156 /* Sign In Menu item for sitemap. */
157 def buildSignInMenu = Menu(Loc(
158 name + " Sign In", mySignIn,
159 name + ".signin", signInLocParams))
160
161 /* Callback Menu item for sitemap. */
162 def buildCallbackMenu = Menu(Loc(
163 name + " Callback", myCallback,
164 name + ".callback", callbackLocParams))
165
166 /*
167 * Sets the client callback.
168 * Sets the referer by default.
169 */
170 protected def setClientCallback {
171 ClientCallback(S.referer.map {
172 r =>
173 info(s"$name client callback set to $r")
174 new java.net.URL(r)
175 })
176 }
177
178 /* Response during sign in. Used by sitemap. */
179 private def signInLocParams = EarlyResponse(() => {
180 setClientCallback
181 signIn
182 }) :: Nil
183
184 /* Response during callback. Used by sitemap. */
185 private def callbackLocParams =
186 EarlyResponse(() => { callback }) :: Nil
187 }