idp.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. package v1
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "github.com/labstack/echo/v4"
  7. "github.com/usememos/memos/internal/util"
  8. "github.com/usememos/memos/store"
  9. )
  10. type IdentityProviderType string
  11. const (
  12. IdentityProviderOAuth2Type IdentityProviderType = "OAUTH2"
  13. )
  14. func (t IdentityProviderType) String() string {
  15. return string(t)
  16. }
  17. type IdentityProviderConfig struct {
  18. OAuth2Config *IdentityProviderOAuth2Config `json:"oauth2Config"`
  19. }
  20. type IdentityProviderOAuth2Config struct {
  21. ClientID string `json:"clientId"`
  22. ClientSecret string `json:"clientSecret"`
  23. AuthURL string `json:"authUrl"`
  24. TokenURL string `json:"tokenUrl"`
  25. UserInfoURL string `json:"userInfoUrl"`
  26. Scopes []string `json:"scopes"`
  27. FieldMapping *FieldMapping `json:"fieldMapping"`
  28. }
  29. type FieldMapping struct {
  30. Identifier string `json:"identifier"`
  31. DisplayName string `json:"displayName"`
  32. Email string `json:"email"`
  33. }
  34. type IdentityProvider struct {
  35. ID int32 `json:"id"`
  36. Name string `json:"name"`
  37. Type IdentityProviderType `json:"type"`
  38. IdentifierFilter string `json:"identifierFilter"`
  39. Config *IdentityProviderConfig `json:"config"`
  40. }
  41. type CreateIdentityProviderRequest struct {
  42. Name string `json:"name"`
  43. Type IdentityProviderType `json:"type"`
  44. IdentifierFilter string `json:"identifierFilter"`
  45. Config *IdentityProviderConfig `json:"config"`
  46. }
  47. type UpdateIdentityProviderRequest struct {
  48. ID int32 `json:"-"`
  49. Type IdentityProviderType `json:"type"`
  50. Name *string `json:"name"`
  51. IdentifierFilter *string `json:"identifierFilter"`
  52. Config *IdentityProviderConfig `json:"config"`
  53. }
  54. func (s *APIV1Service) registerIdentityProviderRoutes(g *echo.Group) {
  55. g.GET("/idp", s.GetIdentityProviderList)
  56. g.POST("/idp", s.CreateIdentityProvider)
  57. g.GET("/idp/:idpId", s.GetIdentityProvider)
  58. g.PATCH("/idp/:idpId", s.UpdateIdentityProvider)
  59. g.DELETE("/idp/:idpId", s.DeleteIdentityProvider)
  60. }
  61. // GetIdentityProviderList godoc
  62. //
  63. // @Summary Get a list of identity providers
  64. // @Description *clientSecret is only available for host user
  65. // @Tags idp
  66. // @Produce json
  67. // @Success 200 {object} []IdentityProvider "List of available identity providers"
  68. // @Failure 500 {object} nil "Failed to find identity provider list | Failed to find user"
  69. // @Router /api/v1/idp [GET]
  70. func (s *APIV1Service) GetIdentityProviderList(c echo.Context) error {
  71. ctx := c.Request().Context()
  72. list, err := s.Store.ListIdentityProviders(ctx, &store.FindIdentityProvider{})
  73. if err != nil {
  74. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find identity provider list").SetInternal(err)
  75. }
  76. userID, ok := c.Get(userIDContextKey).(int32)
  77. isHostUser := false
  78. if ok {
  79. user, err := s.Store.GetUser(ctx, &store.FindUser{
  80. ID: &userID,
  81. })
  82. if err != nil {
  83. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  84. }
  85. if user == nil || user.Role == store.RoleHost {
  86. isHostUser = true
  87. }
  88. }
  89. identityProviderList := []*IdentityProvider{}
  90. for _, item := range list {
  91. identityProvider := convertIdentityProviderFromStore(item)
  92. // data desensitize
  93. if !isHostUser {
  94. identityProvider.Config.OAuth2Config.ClientSecret = ""
  95. }
  96. identityProviderList = append(identityProviderList, identityProvider)
  97. }
  98. return c.JSON(http.StatusOK, identityProviderList)
  99. }
  100. // CreateIdentityProvider godoc
  101. //
  102. // @Summary Create Identity Provider
  103. // @Tags idp
  104. // @Accept json
  105. // @Produce json
  106. // @Param body body CreateIdentityProviderRequest true "Identity provider information"
  107. // @Success 200 {object} store.IdentityProvider "Identity provider information"
  108. // @Failure 401 {object} nil "Missing user in session | Unauthorized"
  109. // @Failure 400 {object} nil "Malformatted post identity provider request"
  110. // @Failure 500 {object} nil "Failed to find user | Failed to create identity provider"
  111. // @Router /api/v1/idp [POST]
  112. func (s *APIV1Service) CreateIdentityProvider(c echo.Context) error {
  113. ctx := c.Request().Context()
  114. userID, ok := c.Get(userIDContextKey).(int32)
  115. if !ok {
  116. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  117. }
  118. user, err := s.Store.GetUser(ctx, &store.FindUser{
  119. ID: &userID,
  120. })
  121. if err != nil {
  122. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  123. }
  124. if user == nil || user.Role != store.RoleHost {
  125. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
  126. }
  127. identityProviderCreate := &CreateIdentityProviderRequest{}
  128. if err := json.NewDecoder(c.Request().Body).Decode(identityProviderCreate); err != nil {
  129. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post identity provider request").SetInternal(err)
  130. }
  131. identityProvider, err := s.Store.CreateIdentityProvider(ctx, &store.IdentityProvider{
  132. Name: identityProviderCreate.Name,
  133. Type: store.IdentityProviderType(identityProviderCreate.Type),
  134. IdentifierFilter: identityProviderCreate.IdentifierFilter,
  135. Config: convertIdentityProviderConfigToStore(identityProviderCreate.Config),
  136. })
  137. if err != nil {
  138. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create identity provider").SetInternal(err)
  139. }
  140. return c.JSON(http.StatusOK, convertIdentityProviderFromStore(identityProvider))
  141. }
  142. // GetIdentityProvider godoc
  143. //
  144. // @Summary Get an identity provider by ID
  145. // @Tags idp
  146. // @Accept json
  147. // @Produce json
  148. // @Param idpId path int true "Identity provider ID"
  149. // @Success 200 {object} store.IdentityProvider "Requested identity provider"
  150. // @Failure 400 {object} nil "ID is not a number: %s"
  151. // @Failure 401 {object} nil "Missing user in session | Unauthorized"
  152. // @Failure 404 {object} nil "Identity provider not found"
  153. // @Failure 500 {object} nil "Failed to find identity provider list | Failed to find user"
  154. // @Router /api/v1/idp/{idpId} [GET]
  155. func (s *APIV1Service) GetIdentityProvider(c echo.Context) error {
  156. ctx := c.Request().Context()
  157. userID, ok := c.Get(userIDContextKey).(int32)
  158. if !ok {
  159. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  160. }
  161. user, err := s.Store.GetUser(ctx, &store.FindUser{
  162. ID: &userID,
  163. })
  164. if err != nil {
  165. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  166. }
  167. if user == nil || user.Role != store.RoleHost {
  168. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
  169. }
  170. identityProviderID, err := util.ConvertStringToInt32(c.Param("idpId"))
  171. if err != nil {
  172. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("idpId"))).SetInternal(err)
  173. }
  174. identityProvider, err := s.Store.GetIdentityProvider(ctx, &store.FindIdentityProvider{
  175. ID: &identityProviderID,
  176. })
  177. if err != nil {
  178. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get identity provider").SetInternal(err)
  179. }
  180. if identityProvider == nil {
  181. return echo.NewHTTPError(http.StatusNotFound, "Identity provider not found")
  182. }
  183. return c.JSON(http.StatusOK, convertIdentityProviderFromStore(identityProvider))
  184. }
  185. // DeleteIdentityProvider godoc
  186. //
  187. // @Summary Delete an identity provider by ID
  188. // @Tags idp
  189. // @Accept json
  190. // @Produce json
  191. // @Param idpId path int true "Identity Provider ID"
  192. // @Success 200 {boolean} true "Identity Provider deleted"
  193. // @Failure 400 {object} nil "ID is not a number: %s | Malformatted patch identity provider request"
  194. // @Failure 401 {object} nil "Missing user in session | Unauthorized"
  195. // @Failure 500 {object} nil "Failed to find user | Failed to patch identity provider"
  196. // @Router /api/v1/idp/{idpId} [DELETE]
  197. func (s *APIV1Service) DeleteIdentityProvider(c echo.Context) error {
  198. ctx := c.Request().Context()
  199. userID, ok := c.Get(userIDContextKey).(int32)
  200. if !ok {
  201. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  202. }
  203. user, err := s.Store.GetUser(ctx, &store.FindUser{
  204. ID: &userID,
  205. })
  206. if err != nil {
  207. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  208. }
  209. if user == nil || user.Role != store.RoleHost {
  210. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
  211. }
  212. identityProviderID, err := util.ConvertStringToInt32(c.Param("idpId"))
  213. if err != nil {
  214. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("idpId"))).SetInternal(err)
  215. }
  216. if err = s.Store.DeleteIdentityProvider(ctx, &store.DeleteIdentityProvider{ID: identityProviderID}); err != nil {
  217. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete identity provider").SetInternal(err)
  218. }
  219. return c.JSON(http.StatusOK, true)
  220. }
  221. // UpdateIdentityProvider godoc
  222. //
  223. // @Summary Update an identity provider by ID
  224. // @Tags idp
  225. // @Accept json
  226. // @Produce json
  227. // @Param idpId path int true "Identity Provider ID"
  228. // @Param body body UpdateIdentityProviderRequest true "Patched identity provider information"
  229. // @Success 200 {object} store.IdentityProvider "Patched identity provider"
  230. // @Failure 400 {object} nil "ID is not a number: %s | Malformatted patch identity provider request"
  231. // @Failure 401 {object} nil "Missing user in session | Unauthorized
  232. // @Failure 500 {object} nil "Failed to find user | Failed to patch identity provider"
  233. // @Router /api/v1/idp/{idpId} [PATCH]
  234. func (s *APIV1Service) UpdateIdentityProvider(c echo.Context) error {
  235. ctx := c.Request().Context()
  236. userID, ok := c.Get(userIDContextKey).(int32)
  237. if !ok {
  238. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  239. }
  240. user, err := s.Store.GetUser(ctx, &store.FindUser{
  241. ID: &userID,
  242. })
  243. if err != nil {
  244. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  245. }
  246. if user == nil || user.Role != store.RoleHost {
  247. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
  248. }
  249. identityProviderID, err := util.ConvertStringToInt32(c.Param("idpId"))
  250. if err != nil {
  251. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("idpId"))).SetInternal(err)
  252. }
  253. identityProviderPatch := &UpdateIdentityProviderRequest{
  254. ID: identityProviderID,
  255. }
  256. if err := json.NewDecoder(c.Request().Body).Decode(identityProviderPatch); err != nil {
  257. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch identity provider request").SetInternal(err)
  258. }
  259. identityProvider, err := s.Store.UpdateIdentityProvider(ctx, &store.UpdateIdentityProvider{
  260. ID: identityProviderPatch.ID,
  261. Type: store.IdentityProviderType(identityProviderPatch.Type),
  262. Name: identityProviderPatch.Name,
  263. IdentifierFilter: identityProviderPatch.IdentifierFilter,
  264. Config: convertIdentityProviderConfigToStore(identityProviderPatch.Config),
  265. })
  266. if err != nil {
  267. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch identity provider").SetInternal(err)
  268. }
  269. return c.JSON(http.StatusOK, convertIdentityProviderFromStore(identityProvider))
  270. }
  271. func convertIdentityProviderFromStore(identityProvider *store.IdentityProvider) *IdentityProvider {
  272. return &IdentityProvider{
  273. ID: identityProvider.ID,
  274. Name: identityProvider.Name,
  275. Type: IdentityProviderType(identityProvider.Type),
  276. IdentifierFilter: identityProvider.IdentifierFilter,
  277. Config: convertIdentityProviderConfigFromStore(identityProvider.Config),
  278. }
  279. }
  280. func convertIdentityProviderConfigFromStore(config *store.IdentityProviderConfig) *IdentityProviderConfig {
  281. return &IdentityProviderConfig{
  282. OAuth2Config: &IdentityProviderOAuth2Config{
  283. ClientID: config.OAuth2Config.ClientID,
  284. ClientSecret: config.OAuth2Config.ClientSecret,
  285. AuthURL: config.OAuth2Config.AuthURL,
  286. TokenURL: config.OAuth2Config.TokenURL,
  287. UserInfoURL: config.OAuth2Config.UserInfoURL,
  288. Scopes: config.OAuth2Config.Scopes,
  289. FieldMapping: &FieldMapping{
  290. Identifier: config.OAuth2Config.FieldMapping.Identifier,
  291. DisplayName: config.OAuth2Config.FieldMapping.DisplayName,
  292. Email: config.OAuth2Config.FieldMapping.Email,
  293. },
  294. },
  295. }
  296. }
  297. func convertIdentityProviderConfigToStore(config *IdentityProviderConfig) *store.IdentityProviderConfig {
  298. return &store.IdentityProviderConfig{
  299. OAuth2Config: &store.IdentityProviderOAuth2Config{
  300. ClientID: config.OAuth2Config.ClientID,
  301. ClientSecret: config.OAuth2Config.ClientSecret,
  302. AuthURL: config.OAuth2Config.AuthURL,
  303. TokenURL: config.OAuth2Config.TokenURL,
  304. UserInfoURL: config.OAuth2Config.UserInfoURL,
  305. Scopes: config.OAuth2Config.Scopes,
  306. FieldMapping: &store.FieldMapping{
  307. Identifier: config.OAuth2Config.FieldMapping.Identifier,
  308. DisplayName: config.OAuth2Config.FieldMapping.DisplayName,
  309. Email: config.OAuth2Config.FieldMapping.Email,
  310. },
  311. },
  312. }
  313. }