@@ -2,17 +2,22 @@ package connector
22
33import (
44 "context"
5+ "fmt"
56 "net/mail"
67
78 v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
89 "github.com/conductorone/baton-sdk/pkg/annotations"
910 _ "github.com/conductorone/baton-sdk/pkg/annotations"
11+ "github.com/conductorone/baton-sdk/pkg/connectorbuilder"
1012 "github.com/conductorone/baton-sdk/pkg/pagination"
1113 enTypes "github.com/conductorone/baton-sdk/pkg/types/entitlement"
1214 "github.com/conductorone/baton-sdk/pkg/types/resource"
1315 "github.com/conductorone/baton-sql-server/pkg/mssqldb"
16+ "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
17+ "go.uber.org/zap"
1418)
1519
20+ // userPrincipalSyncer implements both ResourceSyncer and AccountManager.
1621type userPrincipalSyncer struct {
1722 resourceType * v2.ResourceType
1823 client * mssqldb.Client
@@ -82,6 +87,130 @@ func (d *userPrincipalSyncer) Grants(ctx context.Context, resource *v2.Resource,
8287 return nil , "" , nil , nil
8388}
8489
90+ // CreateAccount creates a SQL Server login and database user for an Active Directory user.
91+ // It implements the AccountManager interface.
92+ func (d * userPrincipalSyncer ) CreateAccount (
93+ ctx context.Context ,
94+ accountInfo * v2.AccountInfo ,
95+ credentialOptions * v2.CredentialOptions ,
96+ ) (connectorbuilder.CreateAccountResponse , []* v2.PlaintextData , annotations.Annotations , error ) {
97+ l := ctxzap .Extract (ctx )
98+
99+ // Extract required username field from profile
100+ usernameVal := accountInfo .Profile .GetFields ()["username" ]
101+ if usernameVal == nil || usernameVal .GetStringValue () == "" {
102+ return nil , nil , nil , fmt .Errorf ("missing required username field" )
103+ }
104+ username := usernameVal .GetStringValue ()
105+
106+ // Extract optional domain field from profile
107+ var domain string
108+ domainVal := accountInfo .Profile .GetFields ()["domain" ]
109+ if domainVal != nil && domainVal .GetStringValue () != "" {
110+ domain = domainVal .GetStringValue ()
111+ }
112+
113+ // Create the Windows login
114+ err := d .client .CreateWindowsLogin (ctx , domain , username )
115+ if err != nil {
116+ l .Error ("Failed to create Windows login" , zap .Error (err ))
117+ return nil , nil , nil , fmt .Errorf ("failed to create Windows login: %w" , err )
118+ }
119+
120+ // Determine the formatted username for the database user
121+ var formattedUsername string
122+ if domain != "" {
123+ formattedUsername = fmt .Sprintf ("%s\\ %s" , domain , username )
124+ } else {
125+ formattedUsername = username
126+ }
127+
128+ // Get list of databases to create users in
129+ databases , _ , err := d .client .ListDatabases (ctx , & mssqldb.Pager {})
130+ if err != nil {
131+ l .Error ("Failed to retrieve databases" , zap .Error (err ))
132+ errMsg := fmt .Sprintf ("Login created successfully, but failed to retrieve databases: %v" , err )
133+ result := & v2.CreateAccountResponse_ActionRequiredResult {
134+ Message : errMsg ,
135+ IsCreateAccountResult : true ,
136+ }
137+ return result , nil , nil , nil
138+ }
139+
140+ // Create user in each database
141+ var dbsCreated []string
142+ for _ , db := range databases {
143+ // Skip system databases
144+ if db .Name == "master" || db .Name == "tempdb" || db .Name == "model" || db .Name == "msdb" {
145+ continue
146+ }
147+
148+ err = d .client .CreateDatabaseUserForPrincipal (ctx , db .Name , formattedUsername )
149+ if err != nil {
150+ l .Error ("Failed to create user in database" ,
151+ zap .String ("database" , db .Name ),
152+ zap .String ("user" , formattedUsername ),
153+ zap .Error (err ))
154+ errMsg := fmt .Sprintf ("Login created successfully, but failed to create user in some databases: %v" , err )
155+ result := & v2.CreateAccountResponse_ActionRequiredResult {
156+ Message : errMsg ,
157+ IsCreateAccountResult : true ,
158+ }
159+ return result , nil , nil , nil
160+ }
161+ dbsCreated = append (dbsCreated , db .Name )
162+ }
163+
164+ // Create a resource for the newly created login
165+ profile := map [string ]interface {}{
166+ "username" : username ,
167+ "domain" : domain ,
168+ "formatted_login" : formattedUsername ,
169+ "databases" : dbsCreated ,
170+ }
171+
172+ // Use email as name if it looks like an email address
173+ var userOpts []resource.UserTraitOption
174+ userOpts = append (userOpts , resource .WithUserProfile (profile ))
175+ userOpts = append (userOpts , resource .WithStatus (v2 .UserTrait_Status_STATUS_ENABLED ))
176+
177+ if _ , err = mail .ParseAddress (username ); err == nil {
178+ userOpts = append (userOpts , resource .WithEmail (username , true ))
179+ }
180+
181+ // Create a resource object to represent the user
182+ resource , err := resource .NewUserResource (
183+ formattedUsername ,
184+ d .ResourceType (ctx ),
185+ formattedUsername , // Use the formatted username as the ID
186+ userOpts ,
187+ )
188+ if err != nil {
189+ l .Error ("Failed to create resource for new user" , zap .Error (err ))
190+ return nil , nil , nil , fmt .Errorf ("failed to create resource for new user: %w" , err )
191+ }
192+
193+ // Return success result with the new user resource
194+ successResult := & v2.CreateAccountResponse_SuccessResult {
195+ Resource : resource ,
196+ IsCreateAccountResult : true ,
197+ }
198+
199+ return successResult , nil , nil , nil
200+ }
201+
202+ // CreateAccountCapabilityDetails returns the capability details for account creation.
203+ func (d * userPrincipalSyncer ) CreateAccountCapabilityDetails (
204+ ctx context.Context ,
205+ ) (* v2.CredentialDetailsAccountProvisioning , annotations.Annotations , error ) {
206+ return & v2.CredentialDetailsAccountProvisioning {
207+ SupportedCredentialOptions : []v2.CapabilityDetailCredentialOption {
208+ v2 .CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_NO_PASSWORD ,
209+ },
210+ PreferredCredentialOption : v2 .CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_NO_PASSWORD ,
211+ }, nil , nil
212+ }
213+
85214func newUserPrincipalSyncer (ctx context.Context , c * mssqldb.Client ) * userPrincipalSyncer {
86215 return & userPrincipalSyncer {
87216 resourceType : resourceTypeUser ,
0 commit comments