to do it correctly is not very simple, although once setup, your done.
Data users or application roles default have database scoped permissions. Database users are linked to Logins and therefore can have server wide permissions from their Login. Application roles aren't linked and can only get server wide permission by using a form of impersonation.
Any way to give these server scope permissions by means of impersonating will fail by default.This is by design.
You have 2 options:
- Tell SQL Server that you as a DBA trust any user in that database to have privilegde escalation to server wide permission if impersonation is used. You acomplish this by setting the option
TRUSTWORTHY
to ON
However if you don't fully understand what this option does first read:
SQL Server 2012 Best Practice Whitepaper (Specifically page 18 and 19 about "Database Ownership and Trust")
- Use signed stored procedures to accomplish your task.
I'll first give you the example of option 1:
CREATE LOGIN [appRoleProxy] WITH PASSWORD ='1234abcd!@'
GO
EXEC sp_addsrvrolemember @loginame='appRoleProxy',
@rolename='securityadmin'
GO
CREATE DATABASE [Stefaan]
go
ALTER DATABASE [Stefaan] SET TRUSTWORTHY ON
GO
USE [Stefaan]
GO
--add the proxy account to the dbo role since your stored procedure will be running in the context of this account and is also accessing your user database.
ALTER ROLE [db_owner] ADD MEMBER [appRoleProxy]
GO
CREATE APPLICATION ROLE appRole WITH PASSWORD ='Passw0rd!';
GO
CREATE PROCEDURE dbo.usp_testproc
@LoginName nvarchar(128),
@Password nvarchar(128),
@DBName nvarchar(128)
WITH EXECUTE AS 'appRoleProxy' --Impersonate a SQL server login with the proper server level permissions
AS
DECLARE @SQL varchar(max)
BEGIN
IF NOT EXISTS ( SELECT name
FROM sys.server_principals
WHERE name = @LoginName )
BEGIN
SET @SQL = 'CREATE LOGIN [' + @LoginName + '] WITH PASSWORD = '''
+ @Password + ''', DEFAULT_DATABASE=[' + @DBNAME
+ '], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF';
EXECUTE(@SQL);
END
IF NOT EXISTS ( SELECT name
FROM sys.database_principals
WHERE name = @LoginName )
BEGIN
SET @SQL = 'CREATE USER [' + @LoginName + '] FOR LOGIN ['
+ @LoginName + ']';
EXECUTE(@SQL);
END
IF EXISTS ( SELECT name
FROM sys.server_principals
WHERE name = @LoginName )
BEGIN
EXEC sp_addsrvrolemember @LoginName, 'securityadmin'
END
END
GO
GRANT EXECUTE ON [dbo].[usp_testproc] TO [appRole]
GO
sp_setapprole @rolename='appRole',
@password='Passw0rd!'
EXEC usp_testproc 'testlogin','password23@','stefaan'
Now for an example of option 2 I'd like to point out a answer a gave on another question:
Msg 4834 “You do not have permission to use the bulk load statement”
A role is an entity that can function as a user and/or as a group. A role WITH LOGIN
can be used as a user, i.e. you can log in with it. Any role can function as a group, including roles that you can also log in as. So "user" and "group" are essentially terms that indicate the intended usage of a role, there's no real distinction between them. Even in the PostgreSQL flavour of SQL the two are used more or less as synonyms. For example, the documentation on CREATE USER
says:
CREATE USER is now an alias for CREATE ROLE.
Granting all ... see the manual for GRANT
. You probably actually want to grant rights to ALL TABLES IN SCHEMA public
rather than all tables in the database.
Granting some rights ... same thing, but instead of GRANT ALL
use GRANT SELECT, INSERT
for example. Again, see the manual.
A role is a user, and/or a group. You can only grant to roles, because roles are all that there is.
Best Answer
If you are talking about Azure SQLDB or Managed instance you can use this script:
SELECT role.name AS RoleName,
member.name AS MemberName
FROM sys.database_role_members rolemember JOIN sys.database_principals AS role
ON rolemember.role_principal_id = role.principal_id
JOIN sys.database_principals AS member
ON rolemember.member_principal_id = member.principal_id order by 1
I hope this helps.