1package mssql
2
3import (
4	"context"
5	"fmt"
6	"strings"
7
8	"github.com/hashicorp/vault/sdk/framework"
9	"github.com/hashicorp/vault/sdk/helper/strutil"
10	"github.com/hashicorp/vault/sdk/logical"
11)
12
13func pathListRoles(b *backend) *framework.Path {
14	return &framework.Path{
15		Pattern: "roles/?$",
16
17		Callbacks: map[logical.Operation]framework.OperationFunc{
18			logical.ListOperation: b.pathRoleList,
19		},
20
21		HelpSynopsis:    pathRoleHelpSyn,
22		HelpDescription: pathRoleHelpDesc,
23	}
24}
25
26func pathRoles(b *backend) *framework.Path {
27	return &framework.Path{
28		Pattern: "roles/" + framework.GenericNameRegex("name"),
29		Fields: map[string]*framework.FieldSchema{
30			"name": &framework.FieldSchema{
31				Type:        framework.TypeString,
32				Description: "Name of the role.",
33			},
34
35			"sql": &framework.FieldSchema{
36				Type:        framework.TypeString,
37				Description: "SQL string to create a role. See help for more info.",
38			},
39		},
40
41		Callbacks: map[logical.Operation]framework.OperationFunc{
42			logical.ReadOperation:   b.pathRoleRead,
43			logical.UpdateOperation: b.pathRoleCreate,
44			logical.DeleteOperation: b.pathRoleDelete,
45		},
46
47		HelpSynopsis:    pathRoleHelpSyn,
48		HelpDescription: pathRoleHelpDesc,
49	}
50}
51
52func (b *backend) Role(ctx context.Context, s logical.Storage, n string) (*roleEntry, error) {
53	entry, err := s.Get(ctx, "role/"+n)
54	if err != nil {
55		return nil, err
56	}
57	if entry == nil {
58		return nil, nil
59	}
60
61	var result roleEntry
62	if err := entry.DecodeJSON(&result); err != nil {
63		return nil, err
64	}
65
66	return &result, nil
67}
68
69func (b *backend) pathRoleDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
70	err := req.Storage.Delete(ctx, "role/"+data.Get("name").(string))
71	if err != nil {
72		return nil, err
73	}
74
75	return nil, nil
76}
77
78func (b *backend) pathRoleRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
79	role, err := b.Role(ctx, req.Storage, data.Get("name").(string))
80	if err != nil {
81		return nil, err
82	}
83	if role == nil {
84		return nil, nil
85	}
86
87	return &logical.Response{
88		Data: map[string]interface{}{
89			"sql": role.SQL,
90		},
91	}, nil
92}
93
94func (b *backend) pathRoleList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
95	entries, err := req.Storage.List(ctx, "role/")
96	if err != nil {
97		return nil, err
98	}
99
100	return logical.ListResponse(entries), nil
101}
102
103func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
104	name := data.Get("name").(string)
105	sql := data.Get("sql").(string)
106
107	// Get our connection
108	db, err := b.DB(ctx, req.Storage)
109	if err != nil {
110		return nil, err
111	}
112
113	// Test the query by trying to prepare it
114	for _, query := range strutil.ParseArbitraryStringSlice(sql, ";") {
115		query = strings.TrimSpace(query)
116		if len(query) == 0 {
117			continue
118		}
119
120		stmt, err := db.Prepare(Query(query, map[string]string{
121			"name":     "foo",
122			"password": "bar",
123		}))
124		if err != nil {
125			return logical.ErrorResponse(fmt.Sprintf(
126				"Error testing query: %s", err)), nil
127		}
128		stmt.Close()
129	}
130
131	// Store it
132	entry, err := logical.StorageEntryJSON("role/"+name, &roleEntry{
133		SQL: sql,
134	})
135	if err != nil {
136		return nil, err
137	}
138	if err := req.Storage.Put(ctx, entry); err != nil {
139		return nil, err
140	}
141	return nil, nil
142}
143
144type roleEntry struct {
145	SQL string `json:"sql"`
146}
147
148const pathRoleHelpSyn = `
149Manage the roles that can be created with this backend.
150`
151
152const pathRoleHelpDesc = `
153This path lets you manage the roles that can be created with this backend.
154
155The "sql" parameter customizes the SQL string used to create the login to
156the server.  The parameter can be a sequence of SQL queries, each semi-colon
157separated. Some substitution will be done to the SQL string for certain keys.
158The names of the variables must be surrounded by "{{" and "}}" to be replaced.
159
160  * "name" - The random username generated for the DB user.
161
162  * "password" - The random password generated for the DB user.
163
164Example SQL query to use:
165
166  CREATE LOGIN [{{name}}] WITH PASSWORD = '{{password}}';
167  CREATE USER [{{name}}] FROM LOGIN [{{name}}];
168  GRANT SELECT, UPDATE, DELETE, INSERT on SCHEMA::dbo TO [{{name}}];
169
170Please see the Microsoft SQL Server manual on the GRANT command to learn how to
171do more fine grained access.
172`
173