123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- package v1
- import (
- "encoding/json"
- "fmt"
- "net/http"
- "github.com/labstack/echo/v4"
- "github.com/usememos/memos/internal/util"
- "github.com/usememos/memos/store"
- )
- type IdentityProviderType string
- const (
- IdentityProviderOAuth2Type IdentityProviderType = "OAUTH2"
- )
- func (t IdentityProviderType) String() string {
- return string(t)
- }
- type IdentityProviderConfig struct {
- OAuth2Config *IdentityProviderOAuth2Config `json:"oauth2Config"`
- }
- type IdentityProviderOAuth2Config struct {
- ClientID string `json:"clientId"`
- ClientSecret string `json:"clientSecret"`
- AuthURL string `json:"authUrl"`
- TokenURL string `json:"tokenUrl"`
- UserInfoURL string `json:"userInfoUrl"`
- Scopes []string `json:"scopes"`
- FieldMapping *FieldMapping `json:"fieldMapping"`
- }
- type FieldMapping struct {
- Identifier string `json:"identifier"`
- DisplayName string `json:"displayName"`
- Email string `json:"email"`
- }
- type IdentityProvider struct {
- ID int32 `json:"id"`
- Name string `json:"name"`
- Type IdentityProviderType `json:"type"`
- IdentifierFilter string `json:"identifierFilter"`
- Config *IdentityProviderConfig `json:"config"`
- }
- type CreateIdentityProviderRequest struct {
- Name string `json:"name"`
- Type IdentityProviderType `json:"type"`
- IdentifierFilter string `json:"identifierFilter"`
- Config *IdentityProviderConfig `json:"config"`
- }
- type UpdateIdentityProviderRequest struct {
- ID int32 `json:"-"`
- Type IdentityProviderType `json:"type"`
- Name *string `json:"name"`
- IdentifierFilter *string `json:"identifierFilter"`
- Config *IdentityProviderConfig `json:"config"`
- }
- func (s *APIV1Service) registerIdentityProviderRoutes(g *echo.Group) {
- g.GET("/idp", s.GetIdentityProviderList)
- g.POST("/idp", s.CreateIdentityProvider)
- g.GET("/idp/:idpId", s.GetIdentityProvider)
- g.PATCH("/idp/:idpId", s.UpdateIdentityProvider)
- g.DELETE("/idp/:idpId", s.DeleteIdentityProvider)
- }
- // GetIdentityProviderList godoc
- //
- // @Summary Get a list of identity providers
- // @Description *clientSecret is only available for host user
- // @Tags idp
- // @Produce json
- // @Success 200 {object} []IdentityProvider "List of available identity providers"
- // @Failure 500 {object} nil "Failed to find identity provider list | Failed to find user"
- // @Router /api/v1/idp [GET]
- func (s *APIV1Service) GetIdentityProviderList(c echo.Context) error {
- ctx := c.Request().Context()
- list, err := s.Store.ListIdentityProviders(ctx, &store.FindIdentityProvider{})
- if err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find identity provider list").SetInternal(err)
- }
- userID, ok := c.Get(userIDContextKey).(int32)
- isHostUser := false
- if ok {
- user, err := s.Store.GetUser(ctx, &store.FindUser{
- ID: &userID,
- })
- if err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
- }
- if user == nil || user.Role == store.RoleHost {
- isHostUser = true
- }
- }
- identityProviderList := []*IdentityProvider{}
- for _, item := range list {
- identityProvider := convertIdentityProviderFromStore(item)
- // data desensitize
- if !isHostUser {
- identityProvider.Config.OAuth2Config.ClientSecret = ""
- }
- identityProviderList = append(identityProviderList, identityProvider)
- }
- return c.JSON(http.StatusOK, identityProviderList)
- }
- // CreateIdentityProvider godoc
- //
- // @Summary Create Identity Provider
- // @Tags idp
- // @Accept json
- // @Produce json
- // @Param body body CreateIdentityProviderRequest true "Identity provider information"
- // @Success 200 {object} store.IdentityProvider "Identity provider information"
- // @Failure 401 {object} nil "Missing user in session | Unauthorized"
- // @Failure 400 {object} nil "Malformatted post identity provider request"
- // @Failure 500 {object} nil "Failed to find user | Failed to create identity provider"
- // @Router /api/v1/idp [POST]
- func (s *APIV1Service) CreateIdentityProvider(c echo.Context) error {
- ctx := c.Request().Context()
- userID, ok := c.Get(userIDContextKey).(int32)
- if !ok {
- return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
- }
- user, err := s.Store.GetUser(ctx, &store.FindUser{
- ID: &userID,
- })
- if err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
- }
- if user == nil || user.Role != store.RoleHost {
- return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
- }
- identityProviderCreate := &CreateIdentityProviderRequest{}
- if err := json.NewDecoder(c.Request().Body).Decode(identityProviderCreate); err != nil {
- return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post identity provider request").SetInternal(err)
- }
- identityProvider, err := s.Store.CreateIdentityProvider(ctx, &store.IdentityProvider{
- Name: identityProviderCreate.Name,
- Type: store.IdentityProviderType(identityProviderCreate.Type),
- IdentifierFilter: identityProviderCreate.IdentifierFilter,
- Config: convertIdentityProviderConfigToStore(identityProviderCreate.Config),
- })
- if err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create identity provider").SetInternal(err)
- }
- return c.JSON(http.StatusOK, convertIdentityProviderFromStore(identityProvider))
- }
- // GetIdentityProvider godoc
- //
- // @Summary Get an identity provider by ID
- // @Tags idp
- // @Accept json
- // @Produce json
- // @Param idpId path int true "Identity provider ID"
- // @Success 200 {object} store.IdentityProvider "Requested identity provider"
- // @Failure 400 {object} nil "ID is not a number: %s"
- // @Failure 401 {object} nil "Missing user in session | Unauthorized"
- // @Failure 404 {object} nil "Identity provider not found"
- // @Failure 500 {object} nil "Failed to find identity provider list | Failed to find user"
- // @Router /api/v1/idp/{idpId} [GET]
- func (s *APIV1Service) GetIdentityProvider(c echo.Context) error {
- ctx := c.Request().Context()
- userID, ok := c.Get(userIDContextKey).(int32)
- if !ok {
- return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
- }
- user, err := s.Store.GetUser(ctx, &store.FindUser{
- ID: &userID,
- })
- if err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
- }
- if user == nil || user.Role != store.RoleHost {
- return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
- }
- identityProviderID, err := util.ConvertStringToInt32(c.Param("idpId"))
- if err != nil {
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("idpId"))).SetInternal(err)
- }
- identityProvider, err := s.Store.GetIdentityProvider(ctx, &store.FindIdentityProvider{
- ID: &identityProviderID,
- })
- if err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get identity provider").SetInternal(err)
- }
- if identityProvider == nil {
- return echo.NewHTTPError(http.StatusNotFound, "Identity provider not found")
- }
- return c.JSON(http.StatusOK, convertIdentityProviderFromStore(identityProvider))
- }
- // DeleteIdentityProvider godoc
- //
- // @Summary Delete an identity provider by ID
- // @Tags idp
- // @Accept json
- // @Produce json
- // @Param idpId path int true "Identity Provider ID"
- // @Success 200 {boolean} true "Identity Provider deleted"
- // @Failure 400 {object} nil "ID is not a number: %s | Malformatted patch identity provider request"
- // @Failure 401 {object} nil "Missing user in session | Unauthorized"
- // @Failure 500 {object} nil "Failed to find user | Failed to patch identity provider"
- // @Router /api/v1/idp/{idpId} [DELETE]
- func (s *APIV1Service) DeleteIdentityProvider(c echo.Context) error {
- ctx := c.Request().Context()
- userID, ok := c.Get(userIDContextKey).(int32)
- if !ok {
- return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
- }
- user, err := s.Store.GetUser(ctx, &store.FindUser{
- ID: &userID,
- })
- if err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
- }
- if user == nil || user.Role != store.RoleHost {
- return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
- }
- identityProviderID, err := util.ConvertStringToInt32(c.Param("idpId"))
- if err != nil {
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("idpId"))).SetInternal(err)
- }
- if err = s.Store.DeleteIdentityProvider(ctx, &store.DeleteIdentityProvider{ID: identityProviderID}); err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete identity provider").SetInternal(err)
- }
- return c.JSON(http.StatusOK, true)
- }
- // UpdateIdentityProvider godoc
- //
- // @Summary Update an identity provider by ID
- // @Tags idp
- // @Accept json
- // @Produce json
- // @Param idpId path int true "Identity Provider ID"
- // @Param body body UpdateIdentityProviderRequest true "Patched identity provider information"
- // @Success 200 {object} store.IdentityProvider "Patched identity provider"
- // @Failure 400 {object} nil "ID is not a number: %s | Malformatted patch identity provider request"
- // @Failure 401 {object} nil "Missing user in session | Unauthorized
- // @Failure 500 {object} nil "Failed to find user | Failed to patch identity provider"
- // @Router /api/v1/idp/{idpId} [PATCH]
- func (s *APIV1Service) UpdateIdentityProvider(c echo.Context) error {
- ctx := c.Request().Context()
- userID, ok := c.Get(userIDContextKey).(int32)
- if !ok {
- return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
- }
- user, err := s.Store.GetUser(ctx, &store.FindUser{
- ID: &userID,
- })
- if err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
- }
- if user == nil || user.Role != store.RoleHost {
- return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
- }
- identityProviderID, err := util.ConvertStringToInt32(c.Param("idpId"))
- if err != nil {
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("idpId"))).SetInternal(err)
- }
- identityProviderPatch := &UpdateIdentityProviderRequest{
- ID: identityProviderID,
- }
- if err := json.NewDecoder(c.Request().Body).Decode(identityProviderPatch); err != nil {
- return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch identity provider request").SetInternal(err)
- }
- identityProvider, err := s.Store.UpdateIdentityProvider(ctx, &store.UpdateIdentityProvider{
- ID: identityProviderPatch.ID,
- Type: store.IdentityProviderType(identityProviderPatch.Type),
- Name: identityProviderPatch.Name,
- IdentifierFilter: identityProviderPatch.IdentifierFilter,
- Config: convertIdentityProviderConfigToStore(identityProviderPatch.Config),
- })
- if err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch identity provider").SetInternal(err)
- }
- return c.JSON(http.StatusOK, convertIdentityProviderFromStore(identityProvider))
- }
- func convertIdentityProviderFromStore(identityProvider *store.IdentityProvider) *IdentityProvider {
- return &IdentityProvider{
- ID: identityProvider.ID,
- Name: identityProvider.Name,
- Type: IdentityProviderType(identityProvider.Type),
- IdentifierFilter: identityProvider.IdentifierFilter,
- Config: convertIdentityProviderConfigFromStore(identityProvider.Config),
- }
- }
- func convertIdentityProviderConfigFromStore(config *store.IdentityProviderConfig) *IdentityProviderConfig {
- return &IdentityProviderConfig{
- OAuth2Config: &IdentityProviderOAuth2Config{
- ClientID: config.OAuth2Config.ClientID,
- ClientSecret: config.OAuth2Config.ClientSecret,
- AuthURL: config.OAuth2Config.AuthURL,
- TokenURL: config.OAuth2Config.TokenURL,
- UserInfoURL: config.OAuth2Config.UserInfoURL,
- Scopes: config.OAuth2Config.Scopes,
- FieldMapping: &FieldMapping{
- Identifier: config.OAuth2Config.FieldMapping.Identifier,
- DisplayName: config.OAuth2Config.FieldMapping.DisplayName,
- Email: config.OAuth2Config.FieldMapping.Email,
- },
- },
- }
- }
- func convertIdentityProviderConfigToStore(config *IdentityProviderConfig) *store.IdentityProviderConfig {
- return &store.IdentityProviderConfig{
- OAuth2Config: &store.IdentityProviderOAuth2Config{
- ClientID: config.OAuth2Config.ClientID,
- ClientSecret: config.OAuth2Config.ClientSecret,
- AuthURL: config.OAuth2Config.AuthURL,
- TokenURL: config.OAuth2Config.TokenURL,
- UserInfoURL: config.OAuth2Config.UserInfoURL,
- Scopes: config.OAuth2Config.Scopes,
- FieldMapping: &store.FieldMapping{
- Identifier: config.OAuth2Config.FieldMapping.Identifier,
- DisplayName: config.OAuth2Config.FieldMapping.DisplayName,
- Email: config.OAuth2Config.FieldMapping.Email,
- },
- },
- }
- }
|