feat: implement idp server (#1111)

* feat: implement idp server

* chore: update
pull/1115/head
boojack 2 years ago committed by GitHub
parent 69726c3925
commit 0f57629d25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,56 @@
package api
type IdentityProviderType string
const (
IdentityProviderOAuth2 IdentityProviderType = "OAUTH2"
)
type IdentityProviderConfig interface{}
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 int `json:"id"`
Name string `json:"name"`
Type IdentityProviderType `json:"type"`
IdentifierFilter string `json:"identifierFilter"`
Config *IdentityProviderConfig `json:"config"`
}
type IdentityProviderCreate struct {
Name string `json:"name"`
Type IdentityProviderType `json:"type"`
IdentifierFilter string `json:"identifierFilter"`
Config *IdentityProviderConfig `json:"config"`
}
type IdentityProviderFind struct {
ID *int
}
type IdentityProviderPatch struct {
ID int
Type IdentityProviderType `json:"type"`
Name *string `json:"name"`
IdentifierFilter *string `json:"identifierFilter"`
Config *IdentityProviderConfig `json:"config"`
}
type IdentityProviderDelete struct {
ID int
}

@ -0,0 +1,178 @@
package server
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/labstack/echo/v4"
"github.com/usememos/memos/api"
"github.com/usememos/memos/common"
"github.com/usememos/memos/store"
)
func (s *Server) registerIdentityProviderRoutes(g *echo.Group) {
g.POST("/idp", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.FindUser(ctx, &api.UserFind{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != api.Host {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
identityProviderCreate := &api.IdentityProviderCreate{}
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.IdentityProviderMessage{
Name: identityProviderCreate.Name,
Type: store.IdentityProviderType(identityProviderCreate.Type),
IdentifierFilter: identityProviderCreate.IdentifierFilter,
Config: (*store.IdentityProviderConfig)(identityProviderCreate.Config),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create identity provider").SetInternal(err)
}
return c.JSON(http.StatusOK, composeResponse(identityProvider))
})
g.PATCH("/idp/:idpId", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.FindUser(ctx, &api.UserFind{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != api.Host {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
identityProviderID, err := strconv.Atoi(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 := &api.IdentityProviderPatch{
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.UpdateIdentityProviderMessage{
ID: identityProviderPatch.ID,
Type: store.IdentityProviderType(identityProviderPatch.Type),
Name: identityProviderPatch.Name,
IdentifierFilter: identityProviderPatch.IdentifierFilter,
Config: (*store.IdentityProviderConfig)(identityProviderPatch.Config),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch identity provider").SetInternal(err)
}
return c.JSON(http.StatusOK, identityProvider)
})
g.GET("/idp", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.FindUser(ctx, &api.UserFind{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
// We should only show identity provider list to host user.
if user == nil || user.Role != api.Host {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
identityProviderMessageList, err := s.Store.ListIdentityProviders(ctx, &store.FindIdentityProviderMessage{})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find identity provider list").SetInternal(err)
}
var identityProviderList []*api.IdentityProvider
for _, identityProviderMessage := range identityProviderMessageList {
identityProviderList = append(identityProviderList, convertIdentityProviderFromStore(identityProviderMessage))
}
return c.JSON(http.StatusOK, composeResponse(identityProviderList))
})
g.DELETE("/idp/:idpId", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.FindUser(ctx, &api.UserFind{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != api.Host {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
identityProviderID, err := strconv.Atoi(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.DeleteIdentityProviderMessage{ID: identityProviderID}); err != nil {
if common.ErrorCode(err) == common.NotFound {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Identity provider ID not found: %d", identityProviderID))
}
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete identity provider").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
})
}
func convertIdentityProviderFromStore(identityProviderMessage *store.IdentityProviderMessage) *api.IdentityProvider {
identityProvider := &api.IdentityProvider{
ID: identityProviderMessage.ID,
Name: identityProviderMessage.Name,
Type: api.IdentityProviderType(identityProviderMessage.Type),
IdentifierFilter: identityProviderMessage.IdentifierFilter,
}
if identityProvider.Type == api.IdentityProviderOAuth2 {
configMessage := any(identityProviderMessage.Config).(*store.IdentityProviderOAuth2Config)
identityProvider.Config = any(&api.IdentityProviderOAuth2Config{
ClientID: configMessage.ClientID,
ClientSecret: configMessage.ClientSecret,
AuthURL: configMessage.AuthURL,
TokenURL: configMessage.TokenURL,
UserInfoURL: configMessage.UserInfoURL,
Scopes: configMessage.Scopes,
FieldMapping: &api.FieldMapping{
Identifier: configMessage.FieldMapping.Identifier,
DisplayName: configMessage.FieldMapping.DisplayName,
Email: configMessage.FieldMapping.Email,
},
}).(*api.IdentityProviderConfig)
}
return identityProvider
}

@ -116,6 +116,7 @@ func NewServer(ctx context.Context, profile *profile.Profile) (*Server, error) {
s.registerResourceRoutes(apiGroup) s.registerResourceRoutes(apiGroup)
s.registerTagRoutes(apiGroup) s.registerTagRoutes(apiGroup)
s.registerStorageRoutes(apiGroup) s.registerStorageRoutes(apiGroup)
s.registerIdentityProviderRoutes(apiGroup)
return s, nil return s, nil
} }

@ -10,10 +10,10 @@ import (
"github.com/usememos/memos/common" "github.com/usememos/memos/common"
) )
type IdentityProvideType string type IdentityProviderType string
const ( const (
IdentityProviderOAuth2 IdentityProvideType = "OAUTH2" IdentityProviderOAuth2 IdentityProviderType = "OAUTH2"
) )
type IdentityProviderConfig interface{} type IdentityProviderConfig interface{}
@ -29,15 +29,15 @@ type IdentityProviderOAuth2Config struct {
} }
type FieldMapping struct { type FieldMapping struct {
Identifier string Identifier string `json:"identifier"`
DisplayName string DisplayName string `json:"displayName"`
Email string Email string `json:"email"`
} }
type IdentityProviderMessage struct { type IdentityProviderMessage struct {
ID int ID int
Name string Name string
Type IdentityProvideType Type IdentityProviderType
IdentifierFilter string IdentifierFilter string
Config *IdentityProviderConfig Config *IdentityProviderConfig
} }
@ -48,7 +48,7 @@ type FindIdentityProviderMessage struct {
type UpdateIdentityProviderMessage struct { type UpdateIdentityProviderMessage struct {
ID int ID int
Type IdentityProvideType Type IdentityProviderType
Name *string Name *string
IdentifierFilter *string IdentifierFilter *string
Config *IdentityProviderConfig Config *IdentityProviderConfig

Loading…
Cancel
Save