You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

311 lines
8.7 KiB

  1. package handlers
  2. import (
  3. "database/sql"
  4. "encoding/base64"
  5. "encoding/json"
  6. "net/http"
  7. "strings"
  8. "github.com/matrix-org/go-neb/api"
  9. "github.com/matrix-org/go-neb/database"
  10. "github.com/matrix-org/go-neb/metrics"
  11. "github.com/matrix-org/go-neb/types"
  12. "github.com/matrix-org/util"
  13. log "github.com/sirupsen/logrus"
  14. )
  15. // RequestAuthSession represents an HTTP handler capable of processing /admin/requestAuthSession requests.
  16. type RequestAuthSession struct {
  17. Db *database.ServiceDB
  18. }
  19. // OnIncomingRequest handles POST requests to /admin/requestAuthSession. The HTTP body MUST be
  20. // a JSON object representing type "api.RequestAuthSessionRequest".
  21. //
  22. // This will return HTTP 400 if there are missing fields or the Realm ID is unknown.
  23. // For the format of the response, see the specific AuthRealm that the Realm ID corresponds to.
  24. //
  25. // Request:
  26. // POST /admin/requestAuthSession
  27. // {
  28. // "RealmID": "github_realm_id",
  29. // "UserID": "@my_user:localhost",
  30. // "Config": {
  31. // // AuthRealm specific config info
  32. // }
  33. // }
  34. // Response:
  35. // HTTP/1.1 200 OK
  36. // {
  37. // // AuthRealm-specific information
  38. // }
  39. func (h *RequestAuthSession) OnIncomingRequest(req *http.Request) util.JSONResponse {
  40. logger := util.GetLogger(req.Context())
  41. if req.Method != "POST" {
  42. return util.MessageResponse(405, "Unsupported Method")
  43. }
  44. var body api.RequestAuthSessionRequest
  45. if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
  46. return util.MessageResponse(400, "Error parsing request JSON")
  47. }
  48. logger.WithFields(log.Fields{
  49. "realm_id": body.RealmID,
  50. "user_id": body.UserID,
  51. }).Print("Incoming auth session request")
  52. if err := body.Check(); err != nil {
  53. logger.WithError(err).Info("Failed Check")
  54. return util.MessageResponse(400, err.Error())
  55. }
  56. realm, err := h.Db.LoadAuthRealm(body.RealmID)
  57. if err != nil {
  58. logger.WithError(err).Info("Failed to LoadAuthRealm")
  59. return util.MessageResponse(400, "Unknown RealmID")
  60. }
  61. response := realm.RequestAuthSession(body.UserID, body.Config)
  62. if response == nil {
  63. logger.WithField("body", body).Error("Failed to RequestAuthSession")
  64. return util.MessageResponse(500, "Failed to request auth session")
  65. }
  66. metrics.IncrementAuthSession(realm.Type())
  67. return util.JSONResponse{
  68. Code: 200,
  69. JSON: response,
  70. }
  71. }
  72. // RemoveAuthSession represents an HTTP handler capable of processing /admin/removeAuthSession requests.
  73. type RemoveAuthSession struct {
  74. Db *database.ServiceDB
  75. }
  76. // OnIncomingRequest handles POST requests to /admin/removeAuthSession.
  77. //
  78. // The JSON object MUST contain the keys "RealmID" and "UserID" to identify the session to remove.
  79. //
  80. // Request
  81. // POST /admin/removeAuthSession
  82. // {
  83. // "RealmID": "github-realm",
  84. // "UserID": "@my_user:localhost"
  85. // }
  86. // Response:
  87. // HTTP/1.1 200 OK
  88. // {}
  89. func (h *RemoveAuthSession) OnIncomingRequest(req *http.Request) util.JSONResponse {
  90. logger := util.GetLogger(req.Context())
  91. if req.Method != "POST" {
  92. return util.MessageResponse(405, "Unsupported Method")
  93. }
  94. var body struct {
  95. RealmID string
  96. UserID string
  97. }
  98. if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
  99. return util.MessageResponse(400, "Error parsing request JSON")
  100. }
  101. logger.WithFields(log.Fields{
  102. "realm_id": body.RealmID,
  103. "user_id": body.UserID,
  104. }).Print("Incoming remove auth session request")
  105. if body.UserID == "" || body.RealmID == "" {
  106. return util.MessageResponse(400, `Must supply a "UserID", a "RealmID"`)
  107. }
  108. _, err := h.Db.LoadAuthRealm(body.RealmID)
  109. if err != nil {
  110. return util.MessageResponse(400, "Unknown RealmID")
  111. }
  112. if err := h.Db.RemoveAuthSession(body.RealmID, body.UserID); err != nil {
  113. logger.WithError(err).Error("Failed to RemoveAuthSession")
  114. return util.MessageResponse(500, "Failed to remove auth session")
  115. }
  116. return util.JSONResponse{
  117. Code: 200,
  118. JSON: struct{}{},
  119. }
  120. }
  121. // RealmRedirect represents an HTTP handler which can process incoming redirects for auth realms.
  122. type RealmRedirect struct {
  123. Db *database.ServiceDB
  124. }
  125. // Handle requests for an auth realm.
  126. //
  127. // The last path segment of the URL MUST be the base64 form of the Realm ID. What response
  128. // this returns depends on the specific AuthRealm implementation.
  129. func (rh *RealmRedirect) Handle(w http.ResponseWriter, req *http.Request) {
  130. segments := strings.Split(req.URL.Path, "/")
  131. // last path segment is the base64d realm ID which we will pass the incoming request to
  132. base64realmID := segments[len(segments)-1]
  133. bytesRealmID, err := base64.RawURLEncoding.DecodeString(base64realmID)
  134. realmID := string(bytesRealmID)
  135. if err != nil {
  136. log.WithError(err).WithField("base64_realm_id", base64realmID).Print(
  137. "Not a b64 encoded string",
  138. )
  139. w.WriteHeader(400)
  140. return
  141. }
  142. realm, err := rh.Db.LoadAuthRealm(realmID)
  143. if err != nil {
  144. log.WithError(err).WithField("realm_id", realmID).Print("Failed to load realm")
  145. w.WriteHeader(404)
  146. return
  147. }
  148. log.WithFields(log.Fields{
  149. "realm_id": realmID,
  150. }).Print("Incoming realm redirect request")
  151. realm.OnReceiveRedirect(w, req)
  152. }
  153. // ConfigureAuthRealm represents an HTTP handler capable of processing /admin/configureAuthRealm requests.
  154. type ConfigureAuthRealm struct {
  155. Db *database.ServiceDB
  156. }
  157. // OnIncomingRequest handles POST requests to /admin/configureAuthRealm. The JSON object
  158. // provided is of type "api.ConfigureAuthRealmRequest".
  159. //
  160. // Request:
  161. // POST /admin/configureAuthRealm
  162. // {
  163. // "ID": "my-realm-id",
  164. // "Type": "github",
  165. // "Config": {
  166. // // Realm-specific configuration information
  167. // }
  168. // }
  169. // Response:
  170. // HTTP/1.1 200 OK
  171. // {
  172. // "ID": "my-realm-id",
  173. // "Type": "github",
  174. // "OldConfig": {
  175. // // Old auth realm config information
  176. // },
  177. // "NewConfig": {
  178. // // New auth realm config information
  179. // },
  180. // }
  181. func (h *ConfigureAuthRealm) OnIncomingRequest(req *http.Request) util.JSONResponse {
  182. logger := util.GetLogger(req.Context())
  183. if req.Method != "POST" {
  184. return util.MessageResponse(405, "Unsupported Method")
  185. }
  186. var body api.ConfigureAuthRealmRequest
  187. if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
  188. return util.MessageResponse(400, "Error parsing request JSON")
  189. }
  190. if err := body.Check(); err != nil {
  191. return util.MessageResponse(400, err.Error())
  192. }
  193. realm, err := types.CreateAuthRealm(body.ID, body.Type, body.Config)
  194. if err != nil {
  195. return util.MessageResponse(400, "Error parsing config JSON")
  196. }
  197. if err = realm.Register(); err != nil {
  198. return util.MessageResponse(400, "Error registering auth realm")
  199. }
  200. oldRealm, err := h.Db.StoreAuthRealm(realm)
  201. if err != nil {
  202. logger.WithError(err).Error("Failed to StoreAuthRealm")
  203. return util.MessageResponse(500, "Error storing realm")
  204. }
  205. return util.JSONResponse{
  206. Code: 200,
  207. JSON: struct {
  208. ID string
  209. Type string
  210. OldConfig types.AuthRealm
  211. NewConfig types.AuthRealm
  212. }{body.ID, body.Type, oldRealm, realm},
  213. }
  214. }
  215. // GetSession represents an HTTP handler capable of processing /admin/getSession requests.
  216. type GetSession struct {
  217. Db *database.ServiceDB
  218. }
  219. // OnIncomingRequest handles POST requests to /admin/getSession.
  220. //
  221. // The JSON object provided MUST have a "RealmID" and "UserID" in order to fetch the
  222. // correct AuthSession. If there is no session for this tuple of realm and user ID,
  223. // a 200 OK is still returned with "Authenticated" set to false.
  224. //
  225. // Request:
  226. // POST /admin/getSession
  227. // {
  228. // "RealmID": "my-realm",
  229. // "UserID": "@my_user:localhost"
  230. // }
  231. // Response:
  232. // HTTP/1.1 200 OK
  233. // {
  234. // "ID": "session_id",
  235. // "Authenticated": true,
  236. // "Info": {
  237. // // Session-specific config info
  238. // }
  239. // }
  240. // Response if session not found:
  241. // HTTP/1.1 200 OK
  242. // {
  243. // "Authenticated": false
  244. // }
  245. func (h *GetSession) OnIncomingRequest(req *http.Request) util.JSONResponse {
  246. logger := util.GetLogger(req.Context())
  247. if req.Method != "POST" {
  248. return util.MessageResponse(405, "Unsupported Method")
  249. }
  250. var body struct {
  251. RealmID string
  252. UserID string
  253. }
  254. if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
  255. return util.MessageResponse(400, "Error parsing request JSON")
  256. }
  257. if body.RealmID == "" || body.UserID == "" {
  258. return util.MessageResponse(400, `Must supply a "RealmID" and "UserID"`)
  259. }
  260. session, err := h.Db.LoadAuthSessionByUser(body.RealmID, body.UserID)
  261. if err != nil && err != sql.ErrNoRows {
  262. logger.WithError(err).WithField("body", body).Error("Failed to LoadAuthSessionByUser")
  263. return util.MessageResponse(500, `Failed to load session`)
  264. }
  265. if err == sql.ErrNoRows {
  266. return util.JSONResponse{
  267. Code: 200,
  268. JSON: struct {
  269. Authenticated bool
  270. }{false},
  271. }
  272. }
  273. return util.JSONResponse{
  274. Code: 200,
  275. JSON: struct {
  276. ID string
  277. Authenticated bool
  278. Info interface{}
  279. }{session.ID(), session.Authenticated(), session.Info()},
  280. }
  281. }