Skip to content

Commit 10e43ed

Browse files
committed
Added MangoDB implementation of ServiceStack's IUserAuthRepository.
1 parent 2b40e56 commit 10e43ed

4 files changed

Lines changed: 450 additions & 0 deletions

File tree

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.Linq;
5+
using System.Text.RegularExpressions;
6+
using ServiceStack.Common;
7+
using ServiceStack.Text;
8+
using ServiceStack.ServiceInterface.Auth;
9+
10+
using MongoDB.Bson;
11+
using MongoDB.Driver;
12+
using MongoDB.Driver.Builders;
13+
14+
namespace ServiceStack.Authentication.MongoDB
15+
{
16+
public class MongoDBAuthRepository : IUserAuthRepository, IClearable
17+
{
18+
19+
//http://stackoverflow.com/questions/3588623/c-sharp-regex-for-a-username-with-a-few-restrictions
20+
public Regex ValidUserNameRegEx = new Regex(@"^(?=.{3,15}$)([A-Za-z0-9][._-]?)*$", RegexOptions.Compiled);
21+
22+
private readonly MongoDatabase mongoDatabase;
23+
24+
public MongoDBAuthRepository(MongoDatabase mongoDatabase)
25+
{
26+
this.mongoDatabase = mongoDatabase;
27+
}
28+
29+
public void CreateMissingTables()
30+
{
31+
if(!mongoDatabase.CollectionExists("UserAuth"))
32+
mongoDatabase.CreateCollection("UserAuth");
33+
34+
if (!mongoDatabase.CollectionExists("UserOAuthProvider"))
35+
mongoDatabase.CreateCollection("UserOAuthProvider");
36+
}
37+
38+
public void DropAndReCreateTables()
39+
{
40+
if (mongoDatabase.CollectionExists("UserAuth"))
41+
mongoDatabase.DropCollection("UserAuth");
42+
mongoDatabase.CreateCollection("UserAuth");
43+
44+
if (mongoDatabase.CollectionExists("UserOAuthProvider"))
45+
mongoDatabase.DropCollection("UserOAuthProvider");
46+
mongoDatabase.CreateCollection("UserOAuthProvider");
47+
}
48+
49+
private void ValidateNewUser(UserAuth newUser, string password)
50+
{
51+
newUser.ThrowIfNull("newUser");
52+
password.ThrowIfNullOrEmpty("password");
53+
54+
if (newUser.UserName.IsNullOrEmpty() && newUser.Email.IsNullOrEmpty())
55+
throw new ArgumentNullException("UserName or Email is required");
56+
57+
if (!newUser.UserName.IsNullOrEmpty())
58+
{
59+
if (!ValidUserNameRegEx.IsMatch(newUser.UserName))
60+
throw new ArgumentException("UserName contains invalid characters", "UserName");
61+
}
62+
}
63+
64+
public UserAuth CreateUserAuth(UserAuth newUser, string password)
65+
{
66+
ValidateNewUser(newUser, password);
67+
68+
AssertNoExistingUser(mongoDatabase, newUser);
69+
70+
var saltedHash = new SaltedHash();
71+
string salt;
72+
string hash;
73+
saltedHash.GetHashAndSaltString(password, out hash, out salt);
74+
var digestHelper = new DigestAuthFunctions();
75+
newUser.DigestHA1Hash = digestHelper.CreateHa1(newUser.UserName, DigestAuthProvider.Realm, password);
76+
newUser.PasswordHash = hash;
77+
newUser.Salt = salt;
78+
newUser.CreatedDate = DateTime.UtcNow;
79+
newUser.ModifiedDate = newUser.CreatedDate;
80+
81+
var collection = mongoDatabase.GetCollection<UserAuth>("UserAuth");
82+
collection.Insert(newUser);
83+
// todo - update id here
84+
return newUser;
85+
}
86+
87+
private static void AssertNoExistingUser(MongoDatabase mongoDatabase, UserAuth newUser, UserAuth exceptForExistingUser = null)
88+
{
89+
if (newUser.UserName != null)
90+
{
91+
var existingUser = GetUserAuthByUserName(mongoDatabase, newUser.UserName);
92+
if (existingUser != null
93+
&& (exceptForExistingUser == null || existingUser.Id != exceptForExistingUser.Id))
94+
throw new ArgumentException("User {0} already exists".Fmt(newUser.UserName));
95+
}
96+
if (newUser.Email != null)
97+
{
98+
var existingUser = GetUserAuthByUserName(mongoDatabase, newUser.Email);
99+
if (existingUser != null
100+
&& (exceptForExistingUser == null || existingUser.Id != exceptForExistingUser.Id))
101+
throw new ArgumentException("Email {0} already exists".Fmt(newUser.Email));
102+
}
103+
}
104+
105+
public UserAuth UpdateUserAuth(UserAuth existingUser, UserAuth newUser, string password)
106+
{
107+
ValidateNewUser(newUser, password);
108+
109+
AssertNoExistingUser(mongoDatabase, newUser, existingUser);
110+
111+
var hash = existingUser.PasswordHash;
112+
var salt = existingUser.Salt;
113+
if (password != null)
114+
{
115+
var saltedHash = new SaltedHash();
116+
saltedHash.GetHashAndSaltString(password, out hash, out salt);
117+
}
118+
// If either one changes the digest hash has to be recalculated
119+
var digestHash = existingUser.DigestHA1Hash;
120+
if (password != null || existingUser.UserName != newUser.UserName)
121+
{
122+
var digestHelper = new DigestAuthFunctions();
123+
digestHash = digestHelper.CreateHa1(newUser.UserName, DigestAuthProvider.Realm, password);
124+
}
125+
newUser.Id = existingUser.Id;
126+
newUser.PasswordHash = hash;
127+
newUser.Salt = salt;
128+
newUser.DigestHA1Hash = digestHash;
129+
newUser.CreatedDate = existingUser.CreatedDate;
130+
newUser.ModifiedDate = DateTime.UtcNow;
131+
132+
var collection = mongoDatabase.GetCollection<UserAuth>("UserAuth");
133+
collection.Insert(newUser);
134+
135+
return newUser;
136+
}
137+
138+
public UserAuth GetUserAuthByUserName(string userNameOrEmail)
139+
{
140+
return GetUserAuthByUserName(mongoDatabase, userNameOrEmail);
141+
}
142+
143+
private static UserAuth GetUserAuthByUserName(MongoDatabase mongoDatabase, string userNameOrEmail)
144+
{
145+
var isEmail = userNameOrEmail.Contains("@");
146+
var collection = mongoDatabase.GetCollection<UserAuth>("UserAuth");
147+
148+
QueryComplete query = isEmail
149+
? Query.EQ("Email", userNameOrEmail)
150+
: Query.EQ("UserName", userNameOrEmail) ;
151+
152+
UserAuth userAuth = collection.FindOne(query);
153+
return userAuth;
154+
}
155+
156+
public bool TryAuthenticate(string userName, string password, out UserAuth userAuth)
157+
{
158+
//userId = null;
159+
userAuth = GetUserAuthByUserName(userName);
160+
if (userAuth == null) return false;
161+
162+
var saltedHash = new SaltedHash();
163+
if (saltedHash.VerifyHashString(password, userAuth.PasswordHash, userAuth.Salt))
164+
{
165+
//userId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
166+
return true;
167+
}
168+
169+
userAuth = null;
170+
return false;
171+
}
172+
public bool TryAuthenticate(Dictionary<string,string> digestHeaders, string PrivateKey, int NonceTimeOut, string sequence, out UserAuth userAuth)
173+
{
174+
//userId = null;
175+
userAuth = GetUserAuthByUserName(digestHeaders["username"]);
176+
if (userAuth == null) return false;
177+
178+
var digestHelper = new DigestAuthFunctions();
179+
if (digestHelper.ValidateResponse(digestHeaders,PrivateKey,NonceTimeOut,userAuth.DigestHA1Hash,sequence))
180+
{
181+
//userId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
182+
return true;
183+
}
184+
userAuth = null;
185+
return false;
186+
}
187+
188+
public void LoadUserAuth(IAuthSession session, IOAuthTokens tokens)
189+
{
190+
session.ThrowIfNull("session");
191+
192+
var userAuth = GetUserAuth(session, tokens);
193+
LoadUserAuth(session, userAuth);
194+
}
195+
196+
private void LoadUserAuth(IAuthSession session, UserAuth userAuth)
197+
{
198+
if (userAuth == null) return;
199+
200+
session.PopulateWith(userAuth);
201+
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
202+
session.ProviderOAuthAccess = GetUserOAuthProviders(session.UserAuthId)
203+
.ConvertAll(x => (IOAuthTokens)x);
204+
205+
}
206+
207+
public UserAuth GetUserAuth(string userAuthId)
208+
{
209+
var collection = mongoDatabase.GetCollection<UserAuth>("UserAuth");
210+
UserAuth userAuth = collection.FindOneById(userAuthId);
211+
return userAuth;
212+
}
213+
214+
public void SaveUserAuth(IAuthSession authSession)
215+
{
216+
217+
var userAuth = !authSession.UserAuthId.IsNullOrEmpty()
218+
? GetUserAuth(authSession.UserAuthId)
219+
: authSession.TranslateTo<UserAuth>();
220+
221+
if (userAuth.Id == default(int) && !authSession.UserAuthId.IsNullOrEmpty())
222+
userAuth.Id = int.Parse(authSession.UserAuthId);
223+
224+
userAuth.ModifiedDate = DateTime.UtcNow;
225+
if (userAuth.CreatedDate == default(DateTime))
226+
userAuth.CreatedDate = userAuth.ModifiedDate;
227+
228+
var collection = mongoDatabase.GetCollection<UserAuth>("UserAuth");
229+
collection.Insert(userAuth);
230+
}
231+
232+
public void SaveUserAuth(UserAuth userAuth)
233+
{
234+
userAuth.ModifiedDate = DateTime.UtcNow;
235+
if (userAuth.CreatedDate == default(DateTime))
236+
userAuth.CreatedDate = userAuth.ModifiedDate;
237+
238+
var collection = mongoDatabase.GetCollection<UserAuth>("UserAuth");
239+
collection.Insert(userAuth);
240+
}
241+
242+
public List<UserOAuthProvider> GetUserOAuthProviders(string userAuthId)
243+
{
244+
var id = int.Parse(userAuthId);
245+
246+
QueryComplete query = Query.EQ("UserAuthId", userAuthId);
247+
248+
var collection = mongoDatabase.GetCollection<UserOAuthProvider>("UserOAuthProvider");
249+
MongoCursor<UserOAuthProvider> queryResult = collection.Find(query);
250+
return queryResult.ToList();
251+
252+
}
253+
254+
public UserAuth GetUserAuth(IAuthSession authSession, IOAuthTokens tokens)
255+
{
256+
if (!authSession.UserAuthId.IsNullOrEmpty())
257+
{
258+
var userAuth = GetUserAuth(authSession.UserAuthId);
259+
if (userAuth != null) return userAuth;
260+
}
261+
if (!authSession.UserAuthName.IsNullOrEmpty())
262+
{
263+
var userAuth = GetUserAuthByUserName(authSession.UserAuthName);
264+
if (userAuth != null) return userAuth;
265+
}
266+
267+
if (tokens == null || tokens.Provider.IsNullOrEmpty() || tokens.UserId.IsNullOrEmpty())
268+
return null;
269+
270+
var query = Query.And(
271+
Query.EQ("Provider", tokens.Provider),
272+
Query.EQ("UserId", tokens.UserId)
273+
);
274+
275+
var providerCollection = mongoDatabase.GetCollection<UserOAuthProvider>("UserOAuthProvider");
276+
var oAuthProvider = providerCollection.FindOne(query);
277+
278+
279+
if (oAuthProvider != null)
280+
{
281+
var userAuthCollection = mongoDatabase.GetCollection<UserAuth>("UserAuth");
282+
var userAuth = userAuthCollection.FindOneById(oAuthProvider.UserAuthId);
283+
return userAuth;
284+
}
285+
return null;
286+
}
287+
288+
public string CreateOrMergeAuthSession(IAuthSession authSession, IOAuthTokens tokens)
289+
{
290+
var userAuth = GetUserAuth(authSession, tokens) ?? new UserAuth();
291+
292+
var query = Query.And(
293+
Query.EQ("Provider", tokens.Provider),
294+
Query.EQ("UserId", tokens.UserId)
295+
);
296+
var providerCollection = mongoDatabase.GetCollection<UserOAuthProvider>("UserOAuthProvider");
297+
var oAuthProvider = providerCollection.FindOne(query);
298+
299+
if (oAuthProvider == null)
300+
{
301+
oAuthProvider = new UserOAuthProvider {
302+
Provider = tokens.Provider,
303+
UserId = tokens.UserId,
304+
};
305+
}
306+
307+
oAuthProvider.PopulateMissing(tokens);
308+
userAuth.PopulateMissing(oAuthProvider);
309+
310+
userAuth.ModifiedDate = DateTime.UtcNow;
311+
if (userAuth.CreatedDate == default(DateTime))
312+
userAuth.CreatedDate = userAuth.ModifiedDate;
313+
314+
var userAuthCollection = mongoDatabase.GetCollection<UserAuth>("UserAuth");
315+
316+
userAuthCollection.Save(userAuth);
317+
318+
oAuthProvider.UserAuthId = userAuth.Id;
319+
320+
if (oAuthProvider.CreatedDate == default(DateTime))
321+
oAuthProvider.CreatedDate = userAuth.ModifiedDate;
322+
oAuthProvider.ModifiedDate = userAuth.ModifiedDate;
323+
324+
providerCollection.Save(oAuthProvider);
325+
326+
return oAuthProvider.UserAuthId.ToString(CultureInfo.InvariantCulture);
327+
}
328+
329+
public void Clear()
330+
{
331+
DropAndReCreateTables();
332+
}
333+
}
334+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Reflection;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
5+
// General Information about an assembly is controlled through the following
6+
// set of attributes. Change these attribute values to modify the information
7+
// associated with an assembly.
8+
[assembly: AssemblyTitle("ServiceStack.Authentication.MongoDB")]
9+
[assembly: AssemblyDescription("")]
10+
[assembly: AssemblyConfiguration("")]
11+
[assembly: AssemblyCompany("Genco")]
12+
[assembly: AssemblyProduct("ServiceStack.Authentication.MongoDB")]
13+
[assembly: AssemblyCopyright("Copyright © Genco 2012")]
14+
[assembly: AssemblyTrademark("")]
15+
[assembly: AssemblyCulture("")]
16+
17+
// Setting ComVisible to false makes the types in this assembly not visible
18+
// to COM components. If you need to access a type in this assembly from
19+
// COM, set the ComVisible attribute to true on that type.
20+
[assembly: ComVisible(false)]
21+
22+
// The following GUID is for the ID of the typelib if this project is exposed to COM
23+
[assembly: Guid("2ea87e37-1c2d-4e2d-af9c-b8be6cc0f485")]
24+
25+
// Version information for an assembly consists of the following four values:
26+
//
27+
// Major Version
28+
// Minor Version
29+
// Build Number
30+
// Revision
31+
//
32+
// You can specify all the values or you can default the Build and Revision Numbers
33+
// by using the '*' as shown below:
34+
// [assembly: AssemblyVersion("1.0.*")]
35+
[assembly: AssemblyVersion("1.0.0.0")]
36+
[assembly: AssemblyFileVersion("1.0.0.0")]

0 commit comments

Comments
 (0)