Tag security

Row-Based Security

In the first two installments of my articles on SQL Server security, I described server level roles and flexible database-level roles. This final article talks about row-based security, specifically row-based security using stored procedures.

Why would you want to use row-based security? Well, there are a lot of situations where you simply can’t use flexible roles and/or server level rows. When you have a web application that potentially has thousands upon thousands (if not millions) of users, it doesn’t make sense to create a SQL Server account for each user and have to maintain all of those users. Instead you need to take a look at how your data is structured and how the users will be interacting with it. Once you’ve done that you’ll probably come back to me and say ‘Okay, now what?’ Well, you probably won’t because odds are you haven’t actually met me.

After taking a careful look at your data and cursing my attempts at humor, you will no doubt notice natural hierarchies and structures to your data. It’s possible to use these structures to control the access to your database. Of course there are some caveats to this approach that will become apparent along the way. Ignore those for a moment and dive right in!

To get started, we’re going to go ahead and create a new database to play around with. Originally I had toyed with the idea of messing around with the usernames of people from twitter and creating a giant masterpiece of fiction involving secret identities and fighting crime. Then I remembered that I hadn’t done anything that creative since creative writing class in college, so I scrapped the idea and went with something a little bit more limited – sales contacts!

Let’s go ahead and create that database:

  /* Set up database */
  USE [master];
  GO

  CREATE DATABASE RowSecurity;
  GO

  /* Create tables */
  USE [RowSecurity];
  GO

  CREATE TABLE Users (
    UserId INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
    Username VARCHAR(50) NOT NULL
  );

  CREATE TABLE Regions (
    RegionId INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
    RegionName VARCHAR(50) NOT NULL
  );

  CREATE TABLE Sites (
    SiteId INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
    RegionId INT NOT NULL REFERENCES Regions(RegionId),
    SiteName VARCHAR(50) NOT NULL
  );

  CREATE TABLE UserRegions (
    UserId INT NOT NULL REFERENCES Users(UserId),
    RegionId INT NOT NULL REFERENCES Regions(RegionId)
  );

Well, that’s not going to do us any good with some empty tables, so let’s add data:

  /* Populate tables with sample data */
  INSERT INTO Users (Username) VALUES ('Alice');
  INSERT INTO Users (Username) VALUES ('Bob');
  INSERT INTO Users (Username) VALUES ('Charlie');
  INSERT INTO Users (Username) VALUES ('David');

  INSERT INTO Regions (RegionName) VALUES ('East');
  INSERT INTO Regions (RegionName) VALUES ('Central');
  INSERT INTO Regions (RegionName) VALUES ('West');

  INSERT INTO Sites (SiteName, RegionId)
  SELECT 'New York City', RegionId
    FROM Regions
   WHERE RegionName = 'East';

  INSERT INTO Sites (SiteName, RegionId)
  SELECT 'Boston', RegionId
    FROM Regions
   WHERE RegionName = 'East';

  INSERT INTO Sites (SiteName, RegionId)
  SELECT 'Charleston', RegionId
    FROM Regions
   WHERE RegionName = 'East';

  INSERT INTO Sites (SiteName, RegionId)
  SELECT 'Chicago', RegionId
    FROM Regions
   WHERE RegionName = 'Central';

  INSERT INTO Sites (SiteName, RegionId)
   SELECT 'Detroit', RegionId
     FROM Regions
    WHERE RegionName = 'Central';

  INSERT INTO Sites (SiteName, RegionId)
   SELECT 'St. Louis', RegionId
     FROM Regions
    WHERE RegionName = 'Central';

  INSERT INTO Sites (SiteName, RegionId)
   SELECT 'Seattle', RegionId
     FROM Regions
    WHERE RegionName = 'West';

  INSERT INTO Sites (SiteName, RegionId)
   SELECT 'Los Angeles', RegionId
     FROM Regions
    WHERE RegionName = 'West';

  INSERT INTO Sites (SiteName, RegionId)
   SELECT 'Provo', RegionId
     FROM Regions
    WHERE RegionName = 'West';

  /* Create records for users and regions */
  INSERT INTO UserRegions (UserId, RegionId)
  SELECT UserId, RegionId
    FROM Users
         CROSS JOIN Regions
   WHERE Username = 'Alice'

  INSERT INTO UserRegions (UserId, RegionId)
  SELECT UserId, RegionId
    FROM Users
         INNER JOIN Regions ON RegionName = 'East'
   WHERE Username = 'Bob'

  INSERT INTO UserRegions (UserId, RegionId)
  SELECT UserId, RegionId
    FROM Users
         INNER JOIN Regions ON RegionName = 'Central'
   WHERE Username = 'Charlie';

  INSERT INTO UserRegions (UserId, RegionId)
  SELECT UserId, RegionId
    FROM Users
         INNER JOIN Regions ON RegionName = 'West'
   WHERE Username = 'David';

Wow, that’s a lot of SQL. Are you still reading? I hope so, because I wrote this all out in longhand and paid someone 15 bucks to transcribe it.

If you want to quickly check out the structure of the data that we have thus far, you can use this handy query:

  /* A query to check the data */
  SELECT u.Username,
         r.RegionName,
         s.SiteName
    FROM dbo.Users AS u
         INNER JOIN dbo.UserRegions AS ur
            ON u.UserId = ur.UserId
         INNER JOIN dbo.Regions AS r
            ON ur.RegionId = r.RegionId
         INNER JOIN dbo.Sites AS s
            ON r.RegionId = s.RegionId
   ORDER BY Username, RegionName, SiteName;

From this query, it’s plain to see that Alice is the evil overlord of the sales empire and can see everything that’s going on within this fictitious organization. The other users are each within a single region.

You’ll notice that I haven’t created any users in the database. As I mentioned earlier, this is a fictitious web application that supports many thousands of users. It simply isn’t feasible to create a SQL Server login for each and every user and maintain those users in addition to all of the other applications on all of the servers. (We’re highly paid DBAs at a major company, remember?) Instead, all of our users are connecting through a single web user account and authenticate against the database.

Now, the way that we’re going to have to enforce security is on a row-by-row level. We can do this through either stored procedures or views. In this case, I’m going to talk about using stored procedures to create our row-based security scheme.

So far, though, we only have enough data to show that we could, in theory, have things secured. There’s nothing to actually secure. Let’s create some data that needs secured.

In our application, we have a business rule which states that a user can only see the sales contacts in their region or regions. First, let’s create our sales contacts and a place to put them:

  /* Create sales contacts table */
  CREATE TABLE SalesContacts (
    SalesContactId INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
    SiteId INT NOT NULL REFERENCES Sites(SiteId),
    Name VARCHAR(50) NOT NULL,
    PhoneNumber VARCHAR(15) NOT NULL
  );

  /* Create sales contacts data
     Names generated from http://www.fakenamegenerator.com/ */
  INSERT INTO SalesContacts VALUES (1, 'Emma L. Atkins', '339-221-1843');
  INSERT INTO SalesContacts VALUES (2, 'Linda R. Oneil', '734-238-6974');
  INSERT INTO SalesContacts VALUES (3, 'Tristan P. Pineda', '201-326-3782');
  INSERT INTO SalesContacts VALUES (4, 'Nicholas M. Garza', '818-917-9238');
  INSERT INTO SalesContacts VALUES (5, 'Ronald C. Villalpando', '603-537-9666');
  INSERT INTO SalesContacts VALUES (6, 'Helen M. Ross', '603-664-8708');
  INSERT INTO SalesContacts VALUES (7, 'Patricia T. Haak', '906-748-3668');
  INSERT INTO SalesContacts VALUES (8, 'Bryant J. Melton', '859-328-7422');
  INSERT INTO SalesContacts VALUES (9, 'Justin J. Norton', '206-835-5862');

Disclaimer: If any of these names or phone numbers are yours, I’m sorry. The internet gave it to me.

Now we have all of our data present. At this point it’s pretty easy to whip up a quick query to return only the appropriate data for a specific user:

  CREATE PROCEDURE SalesContactsForUser(
    @UserId INT
  ) WITH EXECUTE AS OWNER AS
  SET NOCOUNT ON;

  SELECT sc.SalesContactId,
         sc.Name,
         sc.PhoneNumber
    FROM SalesContacts AS sc
         INNER JOIN Sites AS s
            ON sc.SiteId = s.SiteId
         INNER JOIN Regions AS r
            ON s.RegionId = r.RegionId
         INNER JOIN UserRegions AS ur
            ON r.RegionId = ur.RegionId
   WHERE ur.UserId = @UserID;

Now, this is a pretty simple query. In the real world, we might see a large number of joins in our query just to get from the security data to the actual securables. To make these queries easier, you might want to use a materialized, or indexed, view to replace the bulk of the query logic. An indexed view is a view that has been materialized to disk and stored. Basically, you tell SQL Server ‘I want to keep this info on disk because this query is annoying and I know it’s not going to change much.’ SQL Server duly obeys you, and that’s all she wrote. The best information to store in a materialized view is information that rarely changes – security information, for example.

Obviously, in a real-world scenario this situation would be much more complicated with departments within a company coming into play as well as territories within a region. However, if you carefully look at the type of data you’re storing and examine it, you will no doubt find ways where you can create materialized views or pre-materialize queries into tables on a regular schedule in order to meet your performance needs.

Row-based security is not the daunting task that it first appears to be. There are some other articles available online that make this topic appear a lot more complicated than it really is. Rest assured, this is something that is within reach, highly performant, and very easy to implement once you understand the basic principles behind it.

(For your convenience, all of the code in this article is available in one easy to download file: row-based-security.sql)

If you missed the first two posts in this series on SQL Server Security, you can find them at:

Grant EXECUTE Permissions on all Stored Procedures to a Single User

I have to run similar code on a regular basis, so I thought I would share it with everyone.

Basically, it grants access on every stored procedure that meets a certain naming pattern to a single user.

-- I've updated this with Aaron Bertrand's suggestions from the comments.
-- Thanks to Aaron for helping make this better!
DECLARE @sql AS NVARCHAR(MAX);
DECLARE @newline AS NVARCHAR(2);
DECLARE @user_name AS NVARCHAR(100);
DECLARE @sproc_name_pattern AS NVARCHAR(10);

SET @sql = N''
SET @newline = NCHAR(13) + NCHAR(10);
SET @user_name = N'jeremiah';
-- escaping _ prevents it from matching any single character
-- including the wildcard makes this much more portable between DBs
SET @sproc_name_pattern = N'sproc[_]%';

-- using QUOTENAME will properly escape any object names with spaces
-- or other funky characters
SELECT @sql = @sql
              + N'GRANT EXECUTE ON '
              + QUOTENAME(OBJECT_SCHEMA_NAME([object_id])) + '.'
              + QUOTENAME([name])
              + N' TO '
              + QUOTENAME(@user_name)
              + N';'
              + @newline + @newline
  FROM sys.procedures
 WHERE [name] LIKE @sproc_name_pattern;

-- this is my version of debug code, I usually run it once with the PRINT intact
-- before I actually use sp_executesql
--PRINT @sql;

EXEC sp_executesql @sql;

Flexible Database-Level Roles

This is following up from my previous post on Database Roles. In my previous post, I talked about fixed database roles. If you’ve forgotten, go back and re-read the article. I’ll still be here when you get back.

So, what exactly are flexible database roles? They are roles that you create in the database. It’s as simple as that.

When you start with a new database, there are no flexible roles, just the fixed roles. The upside of this is that you are free to create all of the roles that you need and grant all of the permissions that you desire to these roles.

So, where to begin? Let’s start with an empty database:

  -- Create our sample database and switch to it
  CREATE DATABASE TestFixedRoles;
  GO
  USE TestFixedRoles;
  GO

  -- Create a new user and grant them db_securityadmin
  CREATE USER user_security WITHOUT LOGIN;
  CREATE USER user_test WITHOUT LOGIN;
  GO

  EXEC sp_addrolemember N'db_securityadmin', N'user_security';
  GO

So, now we’ve created a new database, a new user, and granted db_securityadmin to our new user. You might remember from the previous article that db_securityadmin can’t grant access to fixed database roles. However, db_securityadmin has no such limitation when dealing with flexible database roles.

Now, let’s set up a test user for the purposes of this introduction to flexible database roles.

  CREATE USER test_user WITHOUT LOGIN;
  GO

This user is now a member of the PUBLIC role. What kind of access does public have?

  CREATE SCHEMA test;
  GO

  CREATE TABLE test.t1 (number INT);
  GO

  DECLARE @i INT;
  SET @i = 0;

  WHILE @i < 1000
  BEGIN
    INSERT INTO test.t1 VALUES (@i);

    SET @i = @i + 1;
  END

  EXECUTE AS USER='test_user';
  GO

  -- This will fail because we have no access to the test schema
  SELECT * FROM t1;
  GO

  REVERT
  GO

None. PUBLIC has no access, thus test_user has no access. Let’s get test_user set up with some access.

  CREATE ROLE test_role;
  GO

  -- The scope qualifier '::' is required.
  GRANT SELECT ON SCHEMA :: test TO test_role;
  GO

  EXEC sp_addrolemember N'test_role', N'test_user';
  GO

  EXECUTE AS USER='test_user';
  GO

  -- Success!
  SELECT * FROM t1;
  GO

  REVERT
  GO

There you have it! We can select from the test schema. How does this help, though, in the real world?

For starters, by creating roles and adding users to roles you can streamline managing security through the use of roles rather than having to monitor the permissions assigned to every user, login, Windows user, and Windows group.

Second, by combining roles for managing security with schemas and stored procedures, it’s possible to carefully control and define granular access to stored procedures and data.

Let’s take a look at AdventureWorks and create an example of how we might want to accomplish this.

This procedure just retrieves customers who placed an order between two dates. Pretty simple.

  CREATE PROCEDURE Sales.GetCustomersWithOrdersBetweenDates (
    @StartDate DATETIME,
    @EndDate DATETIME
  )
  WITH EXECUTE AS OWNER AS

  SELECT pc.LastName,
         pc.FirstName,
         pc.EmailAddress
    FROM Sales.SalesOrderHeader AS ssoh
         INNER JOIN Sales.SalesOrderDetail AS ssod
            ON ssoh.SalesOrderID = ssod.SalesOrderID
         INNER JOIN Sales.Customer AS sc
            ON ssoh.CustomerID = sc.CustomerID
         INNER JOIN Sales.Individual AS si
            ON sc.CustomerID = si.CustomerID
         INNER JOIN Person.Contact AS pc
            ON si.ContactID = pc.ContactID
   WHERE ssoh.OrderDate BETWEEN @StartDate AND @EndDate
   GROUP BY pc.LastName,
            pc.FirstName,
            pc.EmailAddress;
  GO

Next we’ll want to set up some roles:

  -- this is our internal sales personnel
  CREATE ROLE internal_sales_team;
  GO

  -- these are sales people in the field
  CREATE ROLE field_sales_team;
  GO

Now we’ll create users for those roles:

  CREATE USER Iris WITHOUT LOGIN;
  GO

  CREATE USER Frank WITHOUT LOGIN;
  GO

  EXEC sp_addrolemember N'internal_sales_team', N'Iris';
  EXEC sp_addrolemember N'field_sales_team', N'Frank';
  GO

  GRANT EXECUTE ON SCHEMA :: Sales TO internal_sales_team;
  GO

Now let’s test this to see how it works:

  EXECUTE AS USER = N'Iris';
  GO
  EXEC Sales.GetCustomersWithOrdersBetweenDates '20040101', '20040601';
  GO
  REVERT
  GO

  EXECUTE AS USER = N'Frank';
  GO
  EXEC Sales.GetCustomersWithOrdersBetweenDates '20040101', '20040601';
  GO
  REVERT
  GO

Iris can successfully execute the stored procedure, despite her user not having access to the Sales schema because she is a member of the internal_sales_team role which does have execute permissions on the Sales schema. However, Frank cannot execute the stored procedure since the field_sales_team role does not have access to the Sales schema. If you try to run the SQL from this stored procedure as either Frank or Iris the SQL will fail since neither user has select permissions on the Sales or Person schemas.

As I mentioned earlier, through a careful combination of users, logins, roles, Windows users, and Windows groups you can assemble a very secure, robust security infrastructure in SQL Server that can handle a variety of tasks while make your administrative life a lot easier.

SQL Server Security – Database Roles

Database roles (also referred to as database-level roles) are security roles that exist on a database level, as opposed to the server level. If you are familiar with any aspect of system administration, database roles are similar to groups in the world of Windows system administration. Just like a Windows group, when a user is added to a role they inherit all the rights and permissions of the role.

There are two kinds of database roles in SQL Server: fixed roles and flexible roles.

Fixed Roles

Before getting into the fixed roles that are available, it’s important to know what fixed roles actually are. Fixed roles are roles that automatically exist in each database. Adding a user to one of these roles will not change that user’s permissions in any other database.

Any user or role can be added to a database role. Once a user has been added to a role, they can add other users or roles to that role.

A Word of Warning: Be exceptionally carefully when adding flexible roles to a fixed role. You could very easily elevate privileges for a large number of users in one simple step.

With that warning out of the way, let’s take a look at the fixed roles available in SQL Server.

  • db_owner
  • db_securityadmin
  • db_accessadmin
  • db_backupoperator
  • db_ddladmin
  • db_datawriter
  • db_datareader
  • db_denydatawriter
  • db_denydatareader
  • Public

db_owner

Users in the db_owner role have it all, within a single database. They can grant and revoke access, create tables, stored procedures, views, run backups, schedule jobs. Heck, a user who is db_owner can even drop the database.

However, just because you have the keys to the kingdom doesn’t mean that you can do everything. Users who have been granted db_owner will still need specific permissions to run traces and view many of the dynamic management views. Why is that? Those are managed at the server level and will require that server level permissions or roles are granted to your login.

Why use db_owner? You would want to add a user to the db_owner role if you have a user who needs to make extensive modifications to all aspects of a database – creating users, adding them to roles, creating tables/views, creating stored procedures, and adding security settings for the tables, views, and stored procedures. One example of this would be a developer who is creating a database for a new application (or creating extensive modifications to an existing application) but you don’t want to add that user to the sysadmin group.

  -- Create our sample database and switch to it
  CREATE DATABASE TestFixedRoles;
  GO
  USE TestFixedRoles;
  GO

  -- Set up our test users
  CREATE USER user_dbo WITHOUT LOGIN;
  CREATE USER user_security WITHOUT LOGIN;
  CREATE USER user_reader WITHOUT LOGIN;
  CREATE USER user_writer WITHOUT LOGIN;
  CREATE USER user_backup WITHOUT LOGIN;
  GO

  -- Grant db_owner to user_dbo
  EXEC sp_addrolemember N'db_owner', N'user_dbo';
  EXEC sp_addrolemember N'db_securityadmin', N'user_security';

  -- Create an orders table as dbo
  CREATE TABLE orders (
    id            INT       NOT NULL   IDENTITY(1,1),
    orderdate     DATETIME  NOT NULL,
    employee_id   INT       NOT NULL,
    customer_id   INT       NOT NULL,
    quantity      INT       NOT NULL,
    CONSTRAINT PK_Orders PRIMARY KEY (id)
  );
  GO

  -- temporarily switch to the context of user_dbo
  EXECUTE AS USER = 'user_dbo';
  SELECT USER_NAME(); -- This should return user_dbo

  -- This will return 1 row for our orders table
  SELECT t.[name]
    FROM sys.tables AS t
   WHERE t.[name] NOT LIKE 'sys%'

  DROP TABLE orders;

  -- revert back to our regular user
  REVERT;
  GO

db_securityadmin

Users in the db_securityadmin role can modify role permissions and manage permissions. Users in this role have, in theory, almost as much power members of db_owner. The only thing that a member of db_securityadmin can’t do is add users to the db_owner role. Members of db_securityadmin also cannot add users to fixed database roles (this requires membership in the db_owner role).

Why use db_securityadmin? You might want to use db_securityadmin when you need to grant privileges to a trusted user and allow them to manage privileges across an application. Take care when granting db_securityadmin to make sure that you trust the user to not give themselves additional permissions. This risk can be alleviated by adding auditing to the database to log when privileges are granted or revoked.

  EXECUTE AS USER = 'user_security';

  -- this will generate three errors since user_security isn't a member of db_owner.
  EXEC sp_addrolemember N'db_datareader', N'user_reader';
  EXEC sp_addrolemember N'db_datawriter', N'user_writer';
  EXEC sp_addrolemember N'db_backupoperator', N'user_backup';

  REVERT;
  GO

  -- this will now succeed since you are a member of db_owner, after all
  EXEC sp_addrolemember N'db_datareader', N'user_reader';
  EXEC sp_addrolemember N'db_datawriter', N'user_writer';
  EXEC sp_addrolemember N'db_backupoperator', N'user_backup';

db_accessadmin

Members of the db_accessadmin role have the ability to change database access. They can grant and revoke access to Windows logins, Windows groups, and SQL Server logins. The users that they grant access to will be members of the Public role and will have all the privileges associated with that role.

Why use db_accessadmin? This role would be used when you have a user who is responsible for maintaining access to a specific database. When combined with db_securityadmin you have a user who is capable of granting and revoking general access to a database as well as controlling the security permissions for almost any user. This combination is quite powerful and should be granted carefully. With auditing in place, you can mitigate any risk of granting both roles to a single user.

db_backupoperator

Members of this role can create database backups. It’s important to note that they cannot, by default, restore the backups that they create. The only users that can restore a backup are members of the sysadmin and dbcreator server roles and the owner of the database (dbo).

Why use db_backupoperator? If you have an automated process that connects to the database and creates a backup it would be a good idea to have all backup operations connect to the database using a user/login that only has db_backupoperator access to prevent any unauthorized data access due to a user being compromised.

db_datareader/db_denydatareader

Members of the db_datareader role are able to read all data from all user tables. That’s right, everything. Even the super secret table UserPayHistoryAndSocialSecurityNumbers table can be read by members of db_datareader.

Conversely, members of db_denydatareader are explicitly denied the ability to write to any user created tables. They live in the dark about the contents of the database.

Why use db_datareader? Let’s say the accounting department has a separate database. Everyone in accounting is able to create and run ad hoc reports directly against this database, but they shouldn’t be able to do anything else apart from see the contents of the database. Clearly granting db_owner access is out of the question. Your junior DBA is out sick today, so you can’t make him grant SELECT permissions to every table in the database while you go out to Chipotle for lunch. Instead, you can simply grant db_datareader access to the accounting department’s Windows group.

Why use db_denydatareader In addition to the accounting department’s requirement to be able to create ad hoc reports as needed, HR needs to be able to run the canned reports available to them through your carefully crafted stored procedures but they should not be able to run ad hoc reports. However, it turns out that they are also able to create and run ad hoc reports (thankfully your junior DBA emailed you about this yesterday before he got sick). By granting db_denydatareader permissions to the HR Windows group you can prevent members of the HR group from running the ad hoc reports (assuming Windows Forms authentication is being used) and get in touch with the application developers to have them hide ad hoc reports from the HR group in the application front end.

db_datawriter/db_denydatawriter

Much like db_datareader and db_denydatareader, the name of this role is largely self-explanatory: members of the db_datawriter role can INSERT, UPDATE, and DELETE data from any user created table. Likewise, members of the db_denydatawriter role are explicitly denied the ability to perform INSERT, UPDATE, and DELETE operations on any user created tables.

Why use db_datawriter? db_datawriter would be a good choice for a user or login that runs an automated ETL process on a regular basis. This ensures that access to all tables is maintained even when new tables are added and reduces maintenance overhead.

Why use db_denydatawriter If you want to limit write access for a login or user it is easy to add them to the db_denydatawriter role and allow specific access to a subset of tables. This might be desirable when users may need to make adjustments to certain tables – such as a bill, order, or account balance – but they do not need the ability to modify all data in the database.

db_ddladmin

Members of the db_ddladmin role are able to execute DDL commands (CREATE, ALTER, DROP) within the current database. It’s pretty self-explanatory – a member of db_ddladmin can run any CREATE, ALTER, or DROP command within the current database. They cannot create new databases, nor can they alter or drop the current database.

Why use db_ddladmin? Your company has brought on several consultants to help develop a new application. Their work will require that they are able to create, or change, new tables, views, indexes, and stored procedures. However, they don’t need the ability to create users or manage security. In this case, your best solution would be to add the consultants to the db_ddladmin role.

Public

The Public role is a bit different from all of the other roles. Every database user is a member of the Public role. If a user does not have any explicit permissions on a database object, they will inherit the permissions of the Public role.

Why use the public role? Going back to our example of the Accounting and HR departments, let’s say that there are now two databases: Accounting and HR. For the purposes of this example, only Accounting personnel should have access to the Accounting database and only HR personnel should have access to the HR database. What’s the best way to accomplish this?

The first step is to create a user for each department in their respective database. This user needs to be mapped to the appropriate Windows group. Once you have created the user and mapped it to the appropriate group, you can then add the user to the Public role.

Using this method it’s easy to add additional users and groups to the Public role without having to manage separate security settings for each one individually.

  /*****************************************************************************
   * PUBLIC ROLE DEMONSTRATION
   ****************************************************************************/
  CREATE DATABASE Accounting;
  GO
  USE Accounting;
  GO
  -- This is going to fail unless you have a LOGIN called group_Accounting
  CREATE USER user_Accounting FOR LOGIN group_Accounting;
  GO
  EXEC sp_addrolemember N'Public', N'user_Accounting';
  GO

  CREATE DATABASE HumanResources;
  GO
  USE HumanResources;
  GO
  -- This is going to fail unless you have a LOGIN called group_HumanResources
  CREATE USER user_HumanResources FOR LOGIN group_HumanResources;
  GO
  EXEC sp_addrolemember N'Public', N'user_HumanResources';
  GO

This site is protected with Urban Giraffe's plugin 'HTML Purified' and Edward Z. Yang's Powered by HTML Purifier. 219 items have been purified.