Skip to content
Open
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.3.1] - 2024-02-02

- Added `user_type` for supporting either EXTERNAL_GROUP or EXTERNAL_USER in case of Azure SQL database

## [0.3.0] - 2023-12-29

### Changed
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
SHELL := /bin/bash

VERSION = 0.3.0
VERSION = 0.3.1

TERRAFORM = terraform
TERRAFORM_VERSION = "~> 1.5"
Expand Down
1 change: 1 addition & 0 deletions docs/resources/user.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ The following arguments are supported:
* `login_name` - (Optional) The login name of the database user. This must refer to an existing SQL Server login name. Conflicts with the `password` argument. Changing this forces a new resource to be created.
* `default_schema` - (Optional) Specifies the first schema that will be searched by the server when it resolves the names of objects for this database user. Defaults to `dbo`.
* `default_language` - (Optional) Specifies the default language for the user. If no default language is specified, the default language for the user will bed the default language of the database. This argument does not apply to Azure SQL Database or if the user is not a contained database user.
* `user_type` - (Optional) For Azure SQL, only used in azure_login auth context, specify `E` for EXTERNAL_USER of `X` for EXTERNAL_GROUP
* `roles` - (Optional) List of database roles the user has. Defaults to none.

-> If only `username` is specified, an external user is created. The username must be in a format appropriate to the external user created, and will vary between SQL Server types. If `password` is specified, a user that authenticates at the database is created, and if `login_name` is specified, a user that authenticates at the server is created.
Expand Down
1 change: 1 addition & 0 deletions mssql/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const (
databaseProp = "database"
principalIdProp = "principal_id"
usernameProp = "username"
userTypeProp = "user_type"
objectIdProp = "object_id"
passwordProp = "password"
sidStrProp = "sid"
Expand Down
21 changes: 11 additions & 10 deletions mssql/model/user.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package model

type User struct {
PrincipalID int64
Username string
ObjectId string
LoginName string
Password string
SIDStr string
AuthType string
DefaultSchema string
DefaultLanguage string
Roles []string
PrincipalID int64
Username string
UserType string
ObjectId string
LoginName string
Password string
SIDStr string
AuthType string
DefaultSchema string
DefaultLanguage string
Roles []string
}
11 changes: 11 additions & 0 deletions mssql/resource_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ func resourceUser() *schema.Resource {
Optional: true,
ForceNew: true,
},
userTypeProp: {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "E",
},
loginNameProp: {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -107,6 +113,7 @@ func resourceUserCreate(ctx context.Context, data *schema.ResourceData, meta int
database := data.Get(databaseProp).(string)
username := data.Get(usernameProp).(string)
objectId := data.Get(objectIdProp).(string)
userType := data.Get(userTypeProp).(string)
loginName := data.Get(loginNameProp).(string)
password := data.Get(passwordProp).(string)
defaultSchema := data.Get(defaultSchemaProp).(string)
Expand Down Expand Up @@ -136,6 +143,7 @@ func resourceUserCreate(ctx context.Context, data *schema.ResourceData, meta int
user := &model.User{
Username: username,
ObjectId: objectId,
UserType: userType,
LoginName: loginName,
Password: password,
AuthType: authType,
Expand Down Expand Up @@ -174,6 +182,9 @@ func resourceUserRead(ctx context.Context, data *schema.ResourceData, meta inter
logger.Info().Msgf("No user found for [%s].[%s]", database, username)
data.SetId("")
} else {
if err = data.Set(userTypeProp, user.UserType); err != nil {
return diag.FromErr(err)
}
if err = data.Set(loginNameProp, user.LoginName); err != nil {
return diag.FromErr(err)
}
Expand Down
13 changes: 7 additions & 6 deletions sql/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ func (c *Connector) GetUser(ctx context.Context, database, username string) (*mo
' SELECT member_principal_id, drm.role_principal_id FROM [sys].[database_role_members] drm' +
' INNER JOIN CTE_Roles cr ON drm.member_principal_id = cr.role_principal_id' +
') ' +
'SELECT p.principal_id, p.name, p.authentication_type_desc, COALESCE(p.default_schema_name, ''''), COALESCE(p.default_language_name, ''''), p.sid, CONVERT(VARCHAR(1000), p.sid, 1) AS sidStr, '''', COALESCE(STRING_AGG(USER_NAME(r.role_principal_id), '',''), '''') ' +
'SELECT p.principal_id, p.name, p.authentication_type_desc, p.type, COALESCE(p.default_schema_name, ''''), COALESCE(p.default_language_name, ''''), p.sid, CONVERT(VARCHAR(1000), p.sid, 1) AS sidStr, '''', COALESCE(STRING_AGG(USER_NAME(r.role_principal_id), '',''), '''') ' +
'FROM [sys].[database_principals] p' +
' LEFT JOIN CTE_Roles r ON p.principal_id = r.principal_id ' +
'WHERE p.name = ' + QuoteName(@username, '''') + ' ' +
'GROUP BY p.principal_id, p.name, p.authentication_type_desc, p.default_schema_name, p.default_language_name, p.sid'
'GROUP BY p.principal_id, p.name, p.authentication_type_desc, p.type, p.default_schema_name, p.default_language_name, p.sid'
END
ELSE
BEGIN
Expand All @@ -33,12 +33,12 @@ func (c *Connector) GetUser(ctx context.Context, database, username string) (*mo
' SELECT member_principal_id, drm.role_principal_id FROM ' + QuoteName(@database) + '.[sys].[database_role_members] drm' +
' INNER JOIN CTE_Roles cr ON drm.member_principal_id = cr.role_principal_id' +
') ' +
'SELECT p.principal_id, p.name, p.authentication_type_desc, COALESCE(p.default_schema_name, ''''), COALESCE(p.default_language_name, ''''), p.sid, CONVERT(VARCHAR(1000), p.sid, 1) AS sidStr, COALESCE(sl.name, ''''), COALESCE(STRING_AGG(USER_NAME(r.role_principal_id), '',''), '''') ' +
'SELECT p.principal_id, p.name, p.authentication_type_desc, p.type, COALESCE(p.default_schema_name, ''''), COALESCE(p.default_language_name, ''''), p.sid, CONVERT(VARCHAR(1000), p.sid, 1) AS sidStr, COALESCE(sl.name, ''''), COALESCE(STRING_AGG(USER_NAME(r.role_principal_id), '',''), '''') ' +
'FROM ' + QuoteName(@database) + '.[sys].[database_principals] p' +
' LEFT JOIN CTE_Roles r ON p.principal_id = r.principal_id ' +
' LEFT JOIN [master].[sys].[sql_logins] sl ON p.sid = sl.sid ' +
'WHERE p.name = ' + QuoteName(@username, '''') + ' ' +
'GROUP BY p.principal_id, p.name, p.authentication_type_desc, p.default_schema_name, p.default_language_name, p.sid, sl.name'
'GROUP BY p.principal_id, p.name, p.authentication_type_desc, p.type, p.default_schema_name, p.default_language_name, p.sid, sl.name'
END
EXEC (@stmt)`
var (
Expand All @@ -50,7 +50,7 @@ func (c *Connector) GetUser(ctx context.Context, database, username string) (*mo
setDatabase(&database).
QueryRowContext(ctx, cmd,
func(r *sql.Row) error {
return r.Scan(&user.PrincipalID, &user.Username, &user.AuthType, &user.DefaultSchema, &user.DefaultLanguage, &sid, &user.SIDStr, &user.LoginName, &roles)
return r.Scan(&user.PrincipalID, &user.Username, &user.AuthType, &user.UserType, &user.DefaultSchema, &user.DefaultLanguage, &sid, &user.SIDStr, &user.LoginName, &roles)
},
sql.Named("database", database),
sql.Named("username", username),
Expand Down Expand Up @@ -106,7 +106,7 @@ func (c *Connector) CreateUser(ctx context.Context, database string, user *model
BEGIN
IF @objectId != ''
BEGIN
SET @stmt = 'CREATE USER ' + QuoteName(@username) + ' WITH SID=' + CONVERT(varchar(64), CAST(CAST(@objectId AS UNIQUEIDENTIFIER) AS VARBINARY(16)), 1) + ', TYPE=E'
SET @stmt = 'CREATE USER ' + QuoteName(@username) + ' WITH SID=' + CONVERT(varchar(64), CAST(CAST(@objectId AS UNIQUEIDENTIFIER) AS VARBINARY(16)), 1) + ', TYPE=' + QuoteName(@userType)
END
ELSE
BEGIN
Expand Down Expand Up @@ -178,6 +178,7 @@ func (c *Connector) CreateUser(ctx context.Context, database string, user *model
ExecContext(ctx, cmd,
sql.Named("database", database),
sql.Named("username", user.Username),
sql.Named("userType", user.UserType),
sql.Named("objectId", user.ObjectId),
sql.Named("loginName", user.LoginName),
sql.Named("password", user.Password),
Expand Down