Skip to content

Commit

Permalink
Merge pull request #74 from ValeruS/create_login_with_sid
Browse files Browse the repository at this point in the history
creating login with SID
  • Loading branch information
magne committed Mar 27, 2024
2 parents b5224f9 + 2cc46a1 commit 28b19d0
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 5 deletions.
2 changes: 2 additions & 0 deletions docs/resources/login.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The following arguments are supported:
* `server` - (Required) Server and login details for the SQL Server. The attributes supported in the `server` block is detailed below.
* `login_name` - (Required) The name of the server login. Changing this forces a new resource to be created.
* `password` - (Required) The password of the server login.
* `sid` - (Optional) The security identifier (SID).Changing this forces a new resource to be created.
* `default_database` - (Optional) The default database of this server login. Defaults to `master`. This argument does not apply to Azure SQL Database.
* `default_language` - (Optional) The default language of this server login. Defaults to `us_english`. This argument does not apply to Azure SQL Database.

Expand Down Expand Up @@ -55,6 +56,7 @@ The `azuread_managed_identity_auth` block supports the following arguments:
The following attributes are exported:

* `principal_id` - The principal id of this server login.
* `sid` - The security identifier (SID) of this login in String format.

## Import

Expand Down
15 changes: 15 additions & 0 deletions examples/local/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ resource "mssql_login" "example" {
depends_on = [time_sleep.wait_5_seconds]
}

resource "mssql_login" "example" {
server {
host = docker_container.mssql.ip_address
login {
username = local.local_username
password = local.local_password
}
}
login_name = random_password.example.keepers.login_name
password = random_password.example.result
sid = "0xB7BDEF7990D03541BAA2AD73E4FF18E8"

depends_on = [time_sleep.wait_5_seconds]
}

resource "mssql_user" "example" {
server {
host = docker_container.mssql.network_data[0].ip_address
Expand Down
1 change: 1 addition & 0 deletions mssql/model/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package model
type Login struct {
PrincipalID int64
LoginName string
SIDStr string
DefaultDatabase string
DefaultLanguage string
}
17 changes: 15 additions & 2 deletions mssql/resource_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const defaultDatabaseDefault = "master"
const defaultLanguageProp = "default_language"

type LoginConnector interface {
CreateLogin(ctx context.Context, name, password, defaultDatabase, defaultLanguage string) error
CreateLogin(ctx context.Context, name, password, sid, defaultDatabase, defaultLanguage string) error
GetLogin(ctx context.Context, name string) (*model.Login, error)
UpdateLogin(ctx context.Context, name, password, defaultDatabase, defaultLanguage string) error
DeleteLogin(ctx context.Context, name string) error
Expand Down Expand Up @@ -49,6 +49,12 @@ func resourceLogin() *schema.Resource {
Required: true,
Sensitive: true,
},
sidStrProp: {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
defaultDatabaseProp: {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -81,6 +87,7 @@ func resourceLoginCreate(ctx context.Context, data *schema.ResourceData, meta in

loginName := data.Get(loginNameProp).(string)
password := data.Get(passwordProp).(string)
sid := data.Get(sidStrProp).(string)
defaultDatabase := data.Get(defaultDatabaseProp).(string)
defaultLanguage := data.Get(defaultLanguageProp).(string)

Expand All @@ -89,7 +96,7 @@ func resourceLoginCreate(ctx context.Context, data *schema.ResourceData, meta in
return diag.FromErr(err)
}

if err = connector.CreateLogin(ctx, loginName, password, defaultDatabase, defaultLanguage); err != nil {
if err = connector.CreateLogin(ctx, loginName, password, sid, defaultDatabase, defaultLanguage); err != nil {
return diag.FromErr(errors.Wrapf(err, "unable to create login [%s]", loginName))
}

Expand Down Expand Up @@ -122,6 +129,9 @@ func resourceLoginRead(ctx context.Context, data *schema.ResourceData, meta inte
if err = data.Set(principalIdProp, login.PrincipalID); err != nil {
return diag.FromErr(err)
}
if err = data.Set(sidStrProp, login.SIDStr); err != nil {
return diag.FromErr(err)
}
if err = data.Set(defaultDatabaseProp, login.DefaultDatabase); err != nil {
return diag.FromErr(err)
}
Expand Down Expand Up @@ -220,6 +230,9 @@ func resourceLoginImport(ctx context.Context, data *schema.ResourceData, meta in
if err = data.Set(principalIdProp, login.PrincipalID); err != nil {
return nil, err
}
if err = data.Set(sidStrProp, login.SIDStr); err != nil {
return nil, err
}
if err = data.Set(defaultDatabaseProp, login.DefaultDatabase); err != nil {
return nil, err
}
Expand Down
62 changes: 62 additions & 0 deletions mssql/resource_login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,37 @@ func TestAccLogin_Local_Basic(t *testing.T) {
})
}

func TestAccLogin_Local_Basic_SID(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IsUnitTest: runLocalAccTests,
ProviderFactories: testAccProviders,
CheckDestroy: func(state *terraform.State) error { return testAccCheckLoginDestroy(state) },
Steps: []resource.TestStep{
{
Config: testAccCheckLogin(t, "basic", false, map[string]interface{}{"login_name": "login_basic", "password": "valueIsH8kd$¡", "sid": "0xB7BDEF7990D03541BAA2AD73E4FF18E8"}),
Check: resource.ComposeTestCheckFunc(
testAccCheckLoginExists("mssql_login.basic"),
testAccCheckLoginWorks("mssql_login.basic"),
resource.TestCheckResourceAttr("mssql_login.basic", "login_name", "login_basic"),
resource.TestCheckResourceAttr("mssql_login.basic", "password", "valueIsH8kd$¡"),
resource.TestCheckResourceAttr("mssql_login.basic", "sid", "0xB7BDEF7990D03541BAA2AD73E4FF18E8"),
resource.TestCheckResourceAttr("mssql_login.basic", "default_database", "master"),
resource.TestCheckResourceAttr("mssql_login.basic", "default_language", "us_english"),
resource.TestCheckResourceAttr("mssql_login.basic", "server.#", "1"),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.host", "localhost"),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.port", "1433"),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.login.#", "1"),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.login.0.username", os.Getenv("MSSQL_USERNAME")),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.login.0.password", os.Getenv("MSSQL_PASSWORD")),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.azure_login.#", "0"),
resource.TestCheckResourceAttrSet("mssql_login.basic", "principal_id"),
),
},
},
})
}

func TestAccLogin_Azure_Basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -67,6 +98,36 @@ func TestAccLogin_Azure_Basic(t *testing.T) {
})
}

func TestAccLogin_Azure_Basic_SID(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviders,
CheckDestroy: func(state *terraform.State) error { return testAccCheckLoginDestroy(state) },
Steps: []resource.TestStep{
{
Config: testAccCheckLogin(t, "basic", true, map[string]interface{}{"login_name": "login_basic", "password": "valueIsH8kd$¡", "sid": "0x01060000000000640000000000000000BAF5FC800B97EF49AC6FD89469C4987F"}),
Check: resource.ComposeTestCheckFunc(
testAccCheckLoginExists("mssql_login.basic"),
resource.TestCheckResourceAttr("mssql_login.basic", "login_name", "login_basic"),
resource.TestCheckResourceAttr("mssql_login.basic", "password", "valueIsH8kd$¡"),
resource.TestCheckResourceAttr("mssql_login.basic", "sid", "0x01060000000000640000000000000000BAF5FC800B97EF49AC6FD89469C4987F"),
resource.TestCheckResourceAttr("mssql_login.basic", "default_database", "master"),
resource.TestCheckResourceAttr("mssql_login.basic", "default_language", "us_english"),
resource.TestCheckResourceAttr("mssql_login.basic", "server.#", "1"),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.host", os.Getenv("TF_ACC_SQL_SERVER")),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.port", "1433"),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.azure_login.#", "1"),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.azure_login.0.tenant_id", os.Getenv("MSSQL_TENANT_ID")),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.azure_login.0.client_id", os.Getenv("MSSQL_CLIENT_ID")),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.azure_login.0.client_secret", os.Getenv("MSSQL_CLIENT_SECRET")),
resource.TestCheckResourceAttr("mssql_login.basic", "server.0.login.#", "0"),
resource.TestCheckResourceAttrSet("mssql_login.basic", "principal_id"),
),
},
},
})
}

func TestAccLogin_Local_UpdateLoginName(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -225,6 +286,7 @@ func testAccCheckLogin(t *testing.T, name string, azure bool, data map[string]in
}
login_name = "{{ .login_name }}"
password = "{{ .password }}"
{{ with .sid }}sid = "{{ . }}"{{ end }}
{{ with .default_database }}default_database = "{{ . }}"{{ end }}
{{ with .default_language }}default_language = "{{ . }}"{{ end }}
}`
Expand Down
11 changes: 8 additions & 3 deletions sql/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
func (c *Connector) GetLogin(ctx context.Context, name string) (*model.Login, error) {
var login model.Login
err := c.QueryRowContext(ctx,
"SELECT principal_id, name, default_database_name, default_language_name FROM [master].[sys].[sql_logins] WHERE [name] = @name",
"SELECT principal_id, name, CONVERT(VARCHAR(1000), [sid], 1), default_database_name, default_language_name FROM [master].[sys].[sql_logins] WHERE [name] = @name",
func(r *sql.Row) error {
return r.Scan(&login.PrincipalID, &login.LoginName, &login.DefaultDatabase, &login.DefaultLanguage)
return r.Scan(&login.PrincipalID, &login.LoginName, &login.SIDStr, &login.DefaultDatabase, &login.DefaultLanguage)
},
sql.Named("name", name),
)
Expand All @@ -24,10 +24,14 @@ func (c *Connector) GetLogin(ctx context.Context, name string) (*model.Login, er
return &login, nil
}

func (c *Connector) CreateLogin(ctx context.Context, name, password, defaultDatabase, defaultLanguage string) error {
func (c *Connector) CreateLogin(ctx context.Context, name, password, sid, defaultDatabase, defaultLanguage string) error {
cmd := `DECLARE @sql nvarchar(max)
SET @sql = 'CREATE LOGIN ' + QuoteName(@name) + ' ' +
'WITH PASSWORD = ' + QuoteName(@password, '''')
IF NOT @sid = ''
BEGIN
SET @sql = @sql + ', SID = ' + CONVERT(VARCHAR(1000), @sid, 1)
END
IF @@VERSION NOT LIKE 'Microsoft SQL Azure%'
BEGIN
IF @defaultDatabase = '' SET @defaultDatabase = 'master'
Expand All @@ -48,6 +52,7 @@ func (c *Connector) CreateLogin(ctx context.Context, name, password, defaultData
ExecContext(ctx, cmd,
sql.Named("name", name),
sql.Named("password", password),
sql.Named("sid", sid),
sql.Named("defaultDatabase", defaultDatabase),
sql.Named("defaultLanguage", defaultLanguage))
}
Expand Down

0 comments on commit 28b19d0

Please sign in to comment.