@@ -2,14 +2,19 @@ package connector
22
33import (
44 "context"
5+ "crypto/rand"
6+ "fmt"
7+ "math/big"
58
9+ "github.com/conductorone/baton-jira-datacenter/pkg/client"
610 v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
711 "github.com/conductorone/baton-sdk/pkg/annotations"
12+ "github.com/conductorone/baton-sdk/pkg/connectorbuilder"
813 "github.com/conductorone/baton-sdk/pkg/pagination"
914 sdkResource "github.com/conductorone/baton-sdk/pkg/types/resource"
1015 jira "github.com/conductorone/go-jira/v2/onpremise"
11-
12- "github.com/conductorone/baton-jira-datacenter/pkg/client "
16+ "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
17+ "go.uber.org/zap "
1318)
1419
1520type userBuilder struct {
@@ -105,3 +110,146 @@ func newUserBuilder(client *client.Client) *userBuilder {
105110 client : client ,
106111 }
107112}
113+
114+ // CreateAccountCapabilityDetails defines what credential options are supported by this connector.
115+ func (u * userBuilder ) CreateAccountCapabilityDetails (ctx context.Context ) (* v2.CredentialDetailsAccountProvisioning , annotations.Annotations , error ) {
116+ return & v2.CredentialDetailsAccountProvisioning {
117+ SupportedCredentialOptions : []v2.CapabilityDetailCredentialOption {
118+ v2 .CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_RANDOM_PASSWORD ,
119+ },
120+ PreferredCredentialOption : v2 .CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_RANDOM_PASSWORD ,
121+ }, nil , nil
122+ }
123+
124+ // generateRandomPassword creates a secure random password for new user accounts.
125+ func generateRandomPassword (length int ) (string , error ) {
126+ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]"
127+ if length < 8 {
128+ length = 8 // Jira typically requires at least 8 characters
129+ }
130+
131+ password := make ([]byte , length )
132+ for i := 0 ; i < length ; i ++ {
133+ n , err := rand .Int (rand .Reader , big .NewInt (int64 (len (charset ))))
134+ if err != nil {
135+ return "" , err
136+ }
137+ password [i ] = charset [n .Int64 ()]
138+ }
139+
140+ return string (password ), nil
141+ }
142+
143+ // CreateAccount provisions a new user in Jira Datacenter.
144+ func (u * userBuilder ) CreateAccount (
145+ ctx context.Context ,
146+ accountInfo * v2.AccountInfo ,
147+ credentialOptions * v2.CredentialOptions ,
148+ ) (connectorbuilder.CreateAccountResponse , []* v2.PlaintextData , annotations.Annotations , error ) {
149+ l := ctxzap .Extract (ctx )
150+
151+ // Extract account information from the profile
152+ profile := accountInfo .GetProfile ().AsMap ()
153+
154+ // Get required fields
155+ email , ok := profile ["email" ].(string )
156+ if ! ok || email == "" {
157+ return nil , nil , nil , fmt .Errorf ("email is required" )
158+ }
159+
160+ firstName , _ := profile ["first_name" ].(string )
161+ lastName , _ := profile ["last_name" ].(string )
162+
163+ // Create a display name from the first and last name
164+ displayName := firstName
165+ if lastName != "" {
166+ displayName += " " + lastName
167+ }
168+
169+ // If display name is empty, use email as display name
170+ if displayName == "" {
171+ displayName = email
172+ }
173+
174+ // Generate username from email (common practice in Jira)
175+ username := email
176+
177+ // Generate a password if needed
178+ var password string
179+ var plaintextData []* v2.PlaintextData
180+
181+ if credentialOptions .GetRandomPassword () != nil {
182+ // Generate a random password
183+ length := int (credentialOptions .GetRandomPassword ().GetLength ())
184+ if length <= 0 {
185+ length = 12 // Default length
186+ }
187+
188+ var err error
189+ password , err = generateRandomPassword (length )
190+ if err != nil {
191+ return nil , nil , nil , fmt .Errorf ("failed to generate password: %w" , err )
192+ }
193+
194+ // Return the password as plaintext data
195+ plaintextData = append (plaintextData , & v2.PlaintextData {
196+ Name : "password" ,
197+ Description : "Generated password for the new account" ,
198+ Bytes : []byte (password ),
199+ })
200+ } else {
201+ return nil , nil , nil , fmt .Errorf ("random password is required for Jira user creation" )
202+ }
203+
204+ // Create the user request
205+ userRequest := & client.CreateUserRequest {
206+ Name : username ,
207+ Password : password ,
208+ EmailAddress : email ,
209+ DisplayName : displayName ,
210+ Notification : false , // Set to false to avoid sending notification emails
211+ }
212+
213+ // Call the API to create the user
214+ user , err := u .client .CreateUser (ctx , userRequest )
215+ if err != nil {
216+ l .Error ("failed to create user" , zap .Error (err ), zap .String ("email" , email ))
217+ return nil , nil , nil , fmt .Errorf ("failed to create user: %w" , err )
218+ }
219+
220+ l .Info ("user created successfully" ,
221+ zap .String ("username" , username ),
222+ zap .String ("email" , email ),
223+ )
224+
225+ // Add user to the default group if available
226+ if u .client .DefaultGroupName != "" {
227+ _ , err = u .client .AddUserToGroup (ctx , u .client .DefaultGroupName , username )
228+ if err != nil {
229+ l .Warn ("failed to add user to default group" ,
230+ zap .String ("group" , u .client .DefaultGroupName ),
231+ zap .String ("username" , username ),
232+ zap .Error (err ),
233+ )
234+ // Don't fail the whole operation if just the group assignment fails
235+ } else {
236+ l .Info ("added user to default group" ,
237+ zap .String ("group" , u .client .DefaultGroupName ),
238+ zap .String ("username" , username ),
239+ )
240+ }
241+ }
242+
243+ // Create a resource from the new user
244+ resource , err := userResource (* user )
245+ if err != nil {
246+ return nil , nil , nil , fmt .Errorf ("failed to create resource for new user: %w" , err )
247+ }
248+
249+ // Return success result with the new user resource
250+ successResult := & v2.CreateAccountResponse_SuccessResult {
251+ Resource : resource ,
252+ }
253+
254+ return successResult , plaintextData , nil , nil
255+ }
0 commit comments