Skip to content

Commit

Permalink
[entraid] Expose Application OptionalClaims and Groups AD settings
Browse files Browse the repository at this point in the history
This PR exposes Entra ID settings for `OptionalClaims` where applications store the info regarding how the group claim is mapped to SAML properties.

It also exposes the GroupID settings:

- `onPremisesDomainName`
- `onPremisesNetBiosName`
- `onPremisesSamAccountName`

That will be later used compute group claims with the Teleport SAML connector.

Signed-off-by: Tiago Silva <tiago.silva@goteleport.com>
  • Loading branch information
tigrato committed Nov 11, 2024
1 parent 3aa6f3c commit a47392e
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 0 deletions.
13 changes: 13 additions & 0 deletions lib/msgraph/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,19 @@ func (c *Client) UpdateApplication(ctx context.Context, appObjectID string, app
return trace.Wrap(c.patch(ctx, uri.String(), app))
}

// GetApplication returns the application with the given app client ID.
// Note that appID here is the app the application "client ID" ([Application.AppID]) not "object ID" ([Application.ID]).
// Ref: [https://learn.microsoft.com/en-us/graph/api/application-get].
func (c *Client) GetApplication(ctx context.Context, applicationID string) (*Application, error) {
applicationIDFilter := fmt.Sprintf("applications(appId='%s')", applicationID)
uri := c.endpointURI(applicationIDFilter)
out, err := roundtrip[*Application](ctx, c, http.MethodGet, uri.String(), nil)
if err != nil {
return nil, trace.Wrap(err)
}
return out, nil
}

// UpdateServicePrincipal issues a partial update for a [ServicePrincipal].
// Ref: [https://learn.microsoft.com/en-us/graph/api/serviceprincipal-update].
func (c *Client) UpdateServicePrincipal(ctx context.Context, spID string, sp *ServicePrincipal) error {
Expand Down
77 changes: 77 additions & 0 deletions lib/msgraph/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package msgraph
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -474,3 +475,79 @@ func TestIterateGroupMembers(t *testing.T) {
require.Equal(t, "Test Group 1", *group.DisplayName)
}
}

const getApplicationPayload = `
{
"id": "aeee7e9f-57ad-4ea6-a236-cd10b2dbc0b4",
"appId": "d2a39a2a-1636-457f-82f9-c2d76527e20e",
"displayName": "test SAML App",
"groupMembershipClaims": "SecurityGroup",
"identifierUris": [
"goteleport.com"
],
"optionalClaims": {
"accessToken": [],
"idToken": [],
"saml2Token": [
{
"additionalProperties": [
"sam_account_name"
],
"essential": false,
"name": "groups",
"source": null
}
]
}
}`

func TestGetApplication(t *testing.T) {

mux := http.NewServeMux()
appID := "d2a39a2a-1636-457f-82f9-c2d76527e20e"
mux.Handle(fmt.Sprintf("GET /applications(appId='%s')", appID),
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(getApplicationPayload))
}))

srv := httptest.NewServer(mux)
t.Cleanup(func() { srv.Close() })

uri, err := url.Parse(srv.URL)
require.NoError(t, err)
client := &Client{
httpClient: &http.Client{},
tokenProvider: &fakeTokenProvider{},
retryConfig: retryConfig,
baseURL: uri,
pageSize: 2, // smaller page size so we actually fetch multiple pages with our small test payload
}

app, err := client.GetApplication(context.Background(), appID)
require.NoError(t, err)
require.Equal(t, "aeee7e9f-57ad-4ea6-a236-cd10b2dbc0b4", *app.ID)

expectation := &Application{
AppID: toPtr("d2a39a2a-1636-457f-82f9-c2d76527e20e"),
DirectoryObject: DirectoryObject{
DisplayName: toPtr("test SAML App"),
ID: toPtr("aeee7e9f-57ad-4ea6-a236-cd10b2dbc0b4"),
},
GroupMembershipClaims: toPtr("SecurityGroup"),
IdentifierURIs: &[]string{"goteleport.com"},
OptionalClaims: &OptionalClaims{
Saml2Token: []OptionalClaim{
{
AdditionalProperties: &[]string{"sam_account_name"},
Essential: toPtr(false),
Name: toPtr("groups"),
Source: nil,
},
},
},
}
require.EqualValues(t, expectation, app)

}

func toPtr[T any](s T) *T { return &s }
29 changes: 29 additions & 0 deletions lib/msgraph/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,14 @@ type DirectoryObject struct {

type Group struct {
DirectoryObject
// GroupTypes is a list of group type strings.
GroupTypes []string `json:"groupTypes,omitempty"`
// OnPremisesDomainName is the on-premises domain name of the group.
OnPremisesDomainName *string `json:"onPremisesDomainName,omitempty"`
// OnPremisesNetBiosName is the on-premises NetBIOS name of the group.
OnPremisesNetBiosName *string `json:"onPremisesNetBiosName,omitempty"`
// OnPremisesSamAccountName is the on-premises SAM account name of the group.
OnPremisesSamAccountName *string `json:"onPremisesSamAccountName,omitempty"`
}

func (g *Group) IsOffice365Group() bool {
Expand Down Expand Up @@ -64,6 +71,28 @@ type Application struct {
IdentifierURIs *[]string `json:"identifierUris,omitempty"`
Web *WebApplication `json:"web,omitempty"`
GroupMembershipClaims *string `json:"groupMembershipClaims,omitempty"`
OptionalClaims *OptionalClaims `json:"optionalClaims,omitempty"`
}

// OptionalClaim represents an optional claim in a token.
type OptionalClaim struct {
// AdditionalProperties is a list of additional properties.
// Possible values:
// - sam_account_name: sAMAccountName
// - dns_domain_and_sam_account_name: dnsDomainName\sAMAccountName
// - netbios_domain_and_sam_account_name: netbiosDomainName\sAMAccountName
// - emit_as_roles
// - cloud_displayname
AdditionalProperties *[]string `json:"additionalProperties,omitempty"`
Essential *bool `json:"essential,omitempty"`
Name *string `json:"name,omitempty"`
Source *string `json:"source,omitempty"`
}

// OptionalClaims represents optional claims in a token.
// Currently, only SAML2 tokens are supported.
type OptionalClaims struct {
Saml2Token []OptionalClaim `json:"saml2Token,omitempty"`
}

type WebApplication struct {
Expand Down

0 comments on commit a47392e

Please sign in to comment.