/Raven.Database/Server/Security/OAuth/OAuthRequestAuthorizer.cs

https://github.com/jalchr/ravendb · C# · 207 lines · 167 code · 40 blank · 0 comment · 33 complexity · 59f2f4ab3bc84bbb16218410eb729b06 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Security.Principal;
  4. using System.Linq;
  5. using Raven.Abstractions.Data;
  6. using Raven.Database.Server.Abstractions;
  7. using Raven.Database.Extensions;
  8. namespace Raven.Database.Server.Security.OAuth
  9. {
  10. public class OAuthRequestAuthorizer : AbstractRequestAuthorizer
  11. {
  12. public bool Authorize(IHttpContext ctx, bool hasApiKey)
  13. {
  14. var httpRequest = ctx.Request;
  15. var isGetRequest = IsGetRequest(httpRequest.HttpMethod, httpRequest.Url.AbsolutePath);
  16. var allowUnauthenticatedUsers = // we need to auth even if we don't have to, for bundles that want the user
  17. Settings.AnonymousUserAccessMode == AnonymousUserAccessMode.All ||
  18. Settings.AnonymousUserAccessMode == AnonymousUserAccessMode.Admin ||
  19. Settings.AnonymousUserAccessMode == AnonymousUserAccessMode.Get &&
  20. isGetRequest;
  21. var token = GetToken(ctx);
  22. if (token == null)
  23. {
  24. if (allowUnauthenticatedUsers)
  25. return true;
  26. WriteAuthorizationChallenge(ctx, hasApiKey ? 412 : 401, "invalid_request", "The access token is required");
  27. return false;
  28. }
  29. AccessTokenBody tokenBody;
  30. if (!AccessToken.TryParseBody(Settings.OAuthTokenKey, token, out tokenBody))
  31. {
  32. if (allowUnauthenticatedUsers)
  33. return true;
  34. WriteAuthorizationChallenge(ctx, 401, "invalid_token", "The access token is invalid");
  35. return false;
  36. }
  37. if (tokenBody.IsExpired())
  38. {
  39. if (allowUnauthenticatedUsers)
  40. return true;
  41. WriteAuthorizationChallenge(ctx, 401, "invalid_token", "The access token is expired");
  42. return false;
  43. }
  44. var writeAccess = isGetRequest == false;
  45. if(!tokenBody.IsAuthorized(TenantId, writeAccess))
  46. {
  47. if (allowUnauthenticatedUsers)
  48. return true;
  49. WriteAuthorizationChallenge(ctx, 403, "insufficient_scope",
  50. writeAccess ?
  51. "Not authorized for read/write access for tenant " + TenantId :
  52. "Not authorized for tenant " + TenantId);
  53. return false;
  54. }
  55. ctx.User = new OAuthPrincipal(tokenBody, TenantId);
  56. CurrentOperationContext.Headers.Value[Constants.RavenAuthenticatedUser] = tokenBody.UserId;
  57. CurrentOperationContext.User.Value = ctx.User;
  58. return true;
  59. }
  60. public List<string> GetApprovedDatabases(IPrincipal user)
  61. {
  62. var oAuthUser = user as OAuthPrincipal;
  63. if (oAuthUser == null)
  64. return new List<string>();
  65. return oAuthUser.GetApprovedDatabases();
  66. }
  67. public override void Dispose()
  68. {
  69. }
  70. static string GetToken(IHttpContext ctx)
  71. {
  72. const string bearerPrefix = "Bearer ";
  73. var auth = ctx.Request.Headers["Authorization"];
  74. if(auth == null)
  75. {
  76. auth = ctx.Request.GetCookie("OAuth-Token");
  77. if (auth != null)
  78. auth = Uri.UnescapeDataString(auth);
  79. }
  80. if (auth == null || auth.Length <= bearerPrefix.Length ||
  81. !auth.StartsWith(bearerPrefix, StringComparison.OrdinalIgnoreCase))
  82. return null;
  83. var token = auth.Substring(bearerPrefix.Length, auth.Length - bearerPrefix.Length);
  84. return token;
  85. }
  86. void WriteAuthorizationChallenge(IHttpContext ctx, int statusCode, string error, string errorDescription)
  87. {
  88. if (string.IsNullOrEmpty(Settings.OAuthTokenServer) == false)
  89. {
  90. if (Settings.UseDefaultOAuthTokenServer == false)
  91. {
  92. ctx.Response.AddHeader("OAuth-Source", Settings.OAuthTokenServer);
  93. }
  94. else
  95. {
  96. ctx.Response.AddHeader("OAuth-Source", new UriBuilder(Settings.OAuthTokenServer)
  97. {
  98. Host = ctx.Request.Url.Host,
  99. Port = ctx.Request.Url.Port
  100. }.Uri.ToString());
  101. }
  102. }
  103. ctx.Response.StatusCode = statusCode;
  104. ctx.Response.AddHeader("WWW-Authenticate", string.Format("Bearer realm=\"Raven\", error=\"{0}\",error_description=\"{1}\"", error, errorDescription));
  105. }
  106. public IPrincipal GetUser(IHttpContext ctx, bool hasApiKey)
  107. {
  108. var token = GetToken(ctx);
  109. if (token == null)
  110. {
  111. WriteAuthorizationChallenge(ctx, hasApiKey ? 412 : 401, "invalid_request", "The access token is required");
  112. return null;
  113. }
  114. AccessTokenBody tokenBody;
  115. if (!AccessToken.TryParseBody(Settings.OAuthTokenKey, token, out tokenBody))
  116. {
  117. WriteAuthorizationChallenge(ctx, 401, "invalid_token", "The access token is invalid");
  118. return null;
  119. }
  120. return new OAuthPrincipal(tokenBody, null);
  121. }
  122. }
  123. public class OAuthPrincipal : IPrincipal, IIdentity
  124. {
  125. private readonly AccessTokenBody tokenBody;
  126. private readonly string tenantId;
  127. public OAuthPrincipal(AccessTokenBody tokenBody, string tenantId)
  128. {
  129. this.tokenBody = tokenBody;
  130. this.tenantId = tenantId;
  131. }
  132. public bool IsInRole(string role)
  133. {
  134. if ("Administrators".Equals(role, StringComparison.OrdinalIgnoreCase) == false)
  135. return false;
  136. var databaseAccess = tokenBody.AuthorizedDatabases
  137. .Where(x=>
  138. string.Equals(x.TenantId, tenantId, StringComparison.OrdinalIgnoreCase) ||
  139. x.TenantId == "*");
  140. return databaseAccess.Any(access => access.Admin);
  141. }
  142. public IIdentity Identity
  143. {
  144. get { return this; }
  145. }
  146. public string Name
  147. {
  148. get { return tokenBody.UserId; }
  149. }
  150. public string AuthenticationType
  151. {
  152. get { return "OAuth"; }
  153. }
  154. public bool IsAuthenticated
  155. {
  156. get { return true; }
  157. }
  158. public List<string> GetApprovedDatabases()
  159. {
  160. return tokenBody.AuthorizedDatabases.Select(access => access.TenantId).ToList();
  161. }
  162. public AccessTokenBody TokenBody
  163. {
  164. get { return tokenBody; }
  165. }
  166. }
  167. }