mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-18 03:27:16 +02:00
ping: identity and directory providers (#1975)
* ping: add identity provider * ping: implement directory provider * ping, not onelogin * ping, not onelogin * escape path params
This commit is contained in:
parent
00a1cb7456
commit
fd97561ab1
7 changed files with 738 additions and 0 deletions
167
internal/directory/ping/provider.go
Normal file
167
internal/directory/ping/provider.go
Normal file
|
@ -0,0 +1,167 @@
|
|||
// Package ping implements a directory provider for Ping.
|
||||
package ping
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
|
||||
"github.com/pomerium/pomerium/pkg/grpc/directory"
|
||||
)
|
||||
|
||||
// Name is the name of the Ping provider.
|
||||
const Name = "ping"
|
||||
|
||||
// Provider implements a directory provider using the Ping API.
|
||||
type Provider struct {
|
||||
cfg *config
|
||||
mu sync.RWMutex
|
||||
token *oauth2.Token
|
||||
}
|
||||
|
||||
// New creates a new Ping Provider.
|
||||
func New(options ...Option) *Provider {
|
||||
cfg := getConfig(options...)
|
||||
return &Provider{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// User returns a user's directory information.
|
||||
func (p *Provider) User(ctx context.Context, userID, accessToken string) (*directory.User, error) {
|
||||
client, err := p.getClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
au, err := getUser(ctx, client, p.cfg.apiURL, p.cfg.environmentID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &directory.User{
|
||||
Id: au.ID,
|
||||
DisplayName: au.getDisplayName(),
|
||||
Email: au.Email,
|
||||
GroupIds: au.MemberOfGroupIDs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UserGroups returns all the users and groups in the directory.
|
||||
func (p *Provider) UserGroups(ctx context.Context) ([]*directory.Group, []*directory.User, error) {
|
||||
client, err := p.getClient(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
apiGroups, err := getAllGroups(ctx, client, p.cfg.apiURL, p.cfg.environmentID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
directoryUserLookup := map[string]*directory.User{}
|
||||
directoryGroups := make([]*directory.Group, len(apiGroups))
|
||||
for i, ag := range apiGroups {
|
||||
dg := &directory.Group{
|
||||
Id: ag.ID,
|
||||
Name: ag.Name,
|
||||
}
|
||||
|
||||
apiUsers, err := getGroupUsers(ctx, client, p.cfg.apiURL, p.cfg.environmentID, ag.ID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, au := range apiUsers {
|
||||
du, ok := directoryUserLookup[au.ID]
|
||||
if !ok {
|
||||
du = &directory.User{
|
||||
Id: au.ID,
|
||||
DisplayName: au.getDisplayName(),
|
||||
Email: au.Email,
|
||||
}
|
||||
directoryUserLookup[au.ID] = du
|
||||
}
|
||||
du.GroupIds = append(du.GroupIds, ag.ID)
|
||||
}
|
||||
|
||||
directoryGroups[i] = dg
|
||||
}
|
||||
sort.Slice(directoryGroups, func(i, j int) bool {
|
||||
return directoryGroups[i].Id < directoryGroups[j].Id
|
||||
})
|
||||
|
||||
directoryUsers := make([]*directory.User, 0, len(directoryUserLookup))
|
||||
for _, du := range directoryUserLookup {
|
||||
directoryUsers = append(directoryUsers, du)
|
||||
}
|
||||
sort.Slice(directoryUsers, func(i, j int) bool {
|
||||
return directoryUsers[i].Id < directoryUsers[j].Id
|
||||
})
|
||||
|
||||
return directoryGroups, directoryUsers, nil
|
||||
}
|
||||
|
||||
func (p *Provider) getClient(ctx context.Context) (*http.Client, error) {
|
||||
token, err := p.getToken(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := new(http.Client)
|
||||
*client = *p.cfg.httpClient
|
||||
client.Transport = &oauth2.Transport{
|
||||
Source: oauth2.StaticTokenSource(token),
|
||||
Base: p.cfg.httpClient.Transport,
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (p *Provider) getToken(ctx context.Context) (*oauth2.Token, error) {
|
||||
if p.cfg.serviceAccount == nil {
|
||||
return nil, fmt.Errorf("ping: service account is required")
|
||||
}
|
||||
environmentID := p.cfg.serviceAccount.EnvironmentID
|
||||
if environmentID == "" {
|
||||
environmentID = p.cfg.environmentID
|
||||
}
|
||||
if environmentID == "" {
|
||||
return nil, fmt.Errorf("ping: environment ID is required")
|
||||
}
|
||||
|
||||
p.mu.RLock()
|
||||
token := p.token
|
||||
p.mu.RUnlock()
|
||||
|
||||
if token != nil && token.Valid() {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
token = p.token
|
||||
if token != nil && token.Valid() {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
ocfg := &clientcredentials.Config{
|
||||
ClientID: p.cfg.serviceAccount.ClientID,
|
||||
ClientSecret: p.cfg.serviceAccount.ClientSecret,
|
||||
TokenURL: p.cfg.authURL.ResolveReference(&url.URL{
|
||||
Path: fmt.Sprintf("/%s/as/token", environmentID),
|
||||
}).String(),
|
||||
}
|
||||
var err error
|
||||
p.token, err = ocfg.Token(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.token, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue