public bool TryAuthorize(RavenBaseApiController controller, out HttpResponseMessage msg) { var requestUrl = controller.GetRequestUrl(); if (NeverSecret.IsNeverSecretUrl(requestUrl)) { msg = controller.GetEmptyMessage(); return true; } //CORS pre-flight (ignore creds if using cors). if (!String.IsNullOrEmpty(Settings.AccessControlAllowOrigin) && controller.InnerRequest.Method.Method == "OPTIONS") { msg = controller.GetEmptyMessage(); return true; } var oneTimeToken = controller.GetHeader("Single-Use-Auth-Token"); if (string.IsNullOrEmpty(oneTimeToken) == false) { return TryAuthorizeSingleUseAuthToken(controller, oneTimeToken, out msg); } var authHeader = controller.GetHeader("Authorization"); var hasApiKey = "True".Equals(controller.GetHeader("Has-Api-Key"), StringComparison.CurrentCultureIgnoreCase); var hasOAuthTokenInCookie = controller.HasCookie("OAuth-Token"); if (hasApiKey || hasOAuthTokenInCookie || string.IsNullOrEmpty(authHeader) == false && authHeader.StartsWith("Bearer ")) { return oAuthRequestAuthorizer.TryAuthorize(controller, hasApiKey, IgnoreDb.Urls.Contains(requestUrl), out msg); } return windowsRequestAuthorizer.TryAuthorize(controller, IgnoreDb.Urls.Contains(requestUrl), out msg); }
public LogsPushContent(RavenBaseApiController controller) { Connected = true; Id = controller.GetQueryStringValue("id"); if (string.IsNullOrEmpty(Id)) throw new ArgumentException("Id is mandatory"); }
public ChangesPushContent(RavenBaseApiController controller) { Connected = true; ResourceName = controller.TenantName; Id = controller.GetQueryStringValue("id"); if (string.IsNullOrEmpty(Id)) throw new ArgumentException("Id is mandatory"); long coolDownWithDataLossInMiliseconds = 0; long.TryParse(controller.GetQueryStringValue("coolDownWithDataLoss"), out coolDownWithDataLossInMiliseconds); CoolDownWithDataLossInMiliseconds = coolDownWithDataLossInMiliseconds; }
public static bool IsGetRequest(RavenBaseApiController controller) { switch (controller.InnerRequest.Method.Method) { case "GET": case "HEAD": return true; case "POST": var absolutePath = controller.InnerRequest.RequestUri.AbsolutePath; return absolutePath.EndsWith("/queries", StringComparison.Ordinal) || absolutePath.EndsWith("/multi_get", StringComparison.Ordinal) || absolutePath.EndsWith("/multi_get/", StringComparison.Ordinal); default: return false; } }
public async Task<HttpResponseMessage> HandleActualRequest(RavenBaseApiController controller, Func<Task<HttpResponseMessage>> action, Func<HttpException, HttpResponseMessage> onHttpException) { HttpResponseMessage response = null; cancellationToken.ThrowIfCancellationRequested(); Stopwatch sw = Stopwatch.StartNew(); try { Interlocked.Increment(ref concurrentRequests); if (IsWriteRequest(controller.InnerRequest)) { lastWriteRequest = SystemTime.UtcNow; } if (controller.SetupRequestToProperDatabase(this)) { response = await action(); } } catch (HttpException httpException) { response = onHttpException(httpException); } finally { Interlocked.Decrement(ref concurrentRequests); try { FinalizeRequestProcessing(controller, response, sw); } catch (Exception e) { var aggregateException = e as AggregateException; if (aggregateException != null) { e = aggregateException.ExtractSingleInnerException(); } Logger.ErrorException("Could not finalize request properly", e); } } return response; }
public bool TryAuthorize(RavenBaseApiController controller, bool ignoreDb, out HttpResponseMessage msg) { Func<HttpResponseMessage> onRejectingRequest; var tenantId = controller.TenantName ?? Constants.SystemDatabase; var userCreated = TryCreateUser(controller, tenantId, out onRejectingRequest); if (server.SystemConfiguration.AnonymousUserAccessMode == AnonymousUserAccessMode.None && userCreated == false) { msg = onRejectingRequest(); return false; } PrincipalWithDatabaseAccess user = null; if (userCreated) { user = (PrincipalWithDatabaseAccess)controller.User; CurrentOperationContext.Headers.Value[Constants.RavenAuthenticatedUser] = controller.User.Identity.Name; CurrentOperationContext.User.Value = controller.User; // admins always go through if (user.Principal.IsAdministrator(server.SystemConfiguration.AnonymousUserAccessMode)) { msg = controller.GetEmptyMessage(); return true; } // backup operators can go through if (user.Principal.IsBackupOperator(server.SystemConfiguration.AnonymousUserAccessMode)) { msg = controller.GetEmptyMessage(); return true; } } bool isGetRequest = IsGetRequest(controller.InnerRequest.Method.Method, controller.InnerRequest.RequestUri.AbsolutePath); switch (server.SystemConfiguration.AnonymousUserAccessMode) { case AnonymousUserAccessMode.Admin: case AnonymousUserAccessMode.All: msg = controller.GetEmptyMessage(); return true; // if we have, doesn't matter if we have / don't have the user case AnonymousUserAccessMode.Get: if (isGetRequest) { msg = controller.GetEmptyMessage(); return true; } goto case AnonymousUserAccessMode.None; case AnonymousUserAccessMode.None: if (userCreated) { if (string.IsNullOrEmpty(tenantId) == false && tenantId.StartsWith("fs/")) tenantId = tenantId.Substring(3); if (string.IsNullOrEmpty(tenantId) == false && tenantId.StartsWith("counters/")) tenantId = tenantId.Substring(9); if (user.AdminDatabases.Contains(tenantId) || user.AdminDatabases.Contains("*") || ignoreDb) { msg = controller.GetEmptyMessage(); return true; } if (user.ReadWriteDatabases.Contains(tenantId) || user.ReadWriteDatabases.Contains("*")) { msg = controller.GetEmptyMessage(); return true; } if (isGetRequest && (user.ReadOnlyDatabases.Contains(tenantId) || user.ReadOnlyDatabases.Contains("*"))) { msg = controller.GetEmptyMessage(); return true; } } msg = onRejectingRequest(); return false; default: throw new ArgumentOutOfRangeException(); } }
private void TraceTraffic(RavenBaseApiController controller, LogHttpRequestStatsParams logHttpRequestStatsParams, string databaseName) { if (HasAnyHttpTraceEventTransport() == false) return; NotifyTrafficWatch( new TrafficWatchNotification() { RequestUri = logHttpRequestStatsParams.RequestUri, ElapsedMilliseconds = logHttpRequestStatsParams.Stopwatch.ElapsedMilliseconds, CustomInfo = logHttpRequestStatsParams.CustomInfo, HttpMethod = logHttpRequestStatsParams.HttpMethod, ResponseStatusCode = logHttpRequestStatsParams.ResponseStatusCode, TenantName = NormalizeTennantName(databaseName), TimeStamp = SystemTime.UtcNow, InnerRequestsCount = logHttpRequestStatsParams.InnerRequestsCount } ); }
// Cross-Origin Resource Sharing (CORS) is documented here: http://www.w3.org/TR/cors/ public void AddAccessControlHeaders(RavenBaseApiController controller, HttpResponseMessage msg) { var accessControlAllowOrigin = landlord.SystemConfiguration.AccessControlAllowOrigin; if (accessControlAllowOrigin.Count == 0) return; var originHeader = controller.GetHeader("Origin"); if (originHeader == null || originHeader.Contains(controller.InnerRequest.Headers.Host)) return; // no need bool originAllowed = accessControlAllowOrigin.Contains("*") || accessControlAllowOrigin.Contains(originHeader); if (originAllowed) { controller.AddHeader("Access-Control-Allow-Origin", originHeader, msg); } if (controller.InnerRequest.Method.Method != "OPTIONS") return; controller.AddHeader("Access-Control-Allow-Credentials", "true", msg); controller.AddHeader("Access-Control-Max-Age", landlord.SystemConfiguration.AccessControlMaxAge, msg); controller.AddHeader("Access-Control-Allow-Methods", landlord.SystemConfiguration.AccessControlAllowMethods, msg); if (string.IsNullOrEmpty(landlord.SystemConfiguration.AccessControlRequestHeaders)) { // allow whatever headers are being requested var hdr = controller.GetHeader("Access-Control-Request-Headers"); // typically: "x-requested-with" if (hdr != null) controller.AddHeader("Access-Control-Allow-Headers", hdr, msg); } else { controller.AddHeader("Access-Control-Request-Headers", landlord.SystemConfiguration.AccessControlRequestHeaders, msg); } }
HttpResponseMessage WriteAuthorizationChallenge(RavenBaseApiController controller, int statusCode, string error, string errorDescription) { var msg = controller.GetEmptyMessage(); var systemConfiguration = controller.SystemConfiguration; if (string.IsNullOrEmpty(systemConfiguration.OAuthTokenServer) == false) { if (systemConfiguration.UseDefaultOAuthTokenServer == false) { controller.AddHeader("OAuth-Source", systemConfiguration.OAuthTokenServer, msg); } else { controller.AddHeader("OAuth-Source", new UriBuilder(systemConfiguration.OAuthTokenServer) { Host = controller.InnerRequest.RequestUri.Host, Port = controller.InnerRequest.RequestUri.Port }.Uri.ToString(), msg); } } msg.StatusCode = (HttpStatusCode)statusCode; msg.Headers.Add("WWW-Authenticate", string.Format("Bearer realm=\"Raven\", error=\"{0}\",error_description=\"{1}\"", error, errorDescription)); return msg; }
static string GetToken(RavenBaseApiController controller) { const string bearerPrefix = "Bearer "; var auth = controller.GetHeader("Authorization"); if (auth == null) { auth = controller.GetCookie("OAuth-Token"); if (auth != null) auth = Uri.UnescapeDataString(auth); } if (auth == null || auth.Length <= bearerPrefix.Length || !auth.StartsWith(bearerPrefix, StringComparison.OrdinalIgnoreCase)) return null; var token = auth.Substring(bearerPrefix.Length, auth.Length - bearerPrefix.Length); return token; }
private void LogHttpRequestStats(RavenBaseApiController controller, LogHttpRequestStatsParams logHttpRequestStatsParams, string databaseName, long curReq) { if (Logger.IsDebugEnabled == false) return; if (controller is StudioController || controller is HardRouteController || controller is SilverlightController) return; var message = string.Format(CultureInfo.InvariantCulture, "Request #{0,4:#,0}: {1,-7} - {2,5:#,0} ms - {5,-10} - {3} - {4}", curReq, logHttpRequestStatsParams.HttpMethod, logHttpRequestStatsParams.Stopwatch.ElapsedMilliseconds, logHttpRequestStatsParams.ResponseStatusCode, logHttpRequestStatsParams.RequestUri, databaseName); Logger.Debug(message); if (string.IsNullOrWhiteSpace(logHttpRequestStatsParams.CustomInfo) == false) { Logger.Debug(logHttpRequestStatsParams.CustomInfo); } }
public ConnectionState For(string id, RavenBaseApiController controller = null) { return connections.GetOrAdd(id, _ => { IEventsTransport eventsTransport = null; if (controller != null) eventsTransport = new ChangesPushContent(controller); var connectionState = new ConnectionState(eventsTransport); TimeSensitiveStore.Missing(id); return connectionState; }); }
public HttpTracePushContent(RavenBaseApiController controller) { Connected = true; }
public AdminLogsConnectionState For(string id, RavenBaseApiController controller = null) { var connection = connections.GetOrAdd( id, _ => { IEventsTransport logsTransport = null; if (controller != null) logsTransport = new LogsPushContent(controller); var connectionState = new AdminLogsConnectionState(logsTransport); TimeSensitiveStore.Missing(id); return connectionState; }); AlterEnabled(); return connection; }
// Cross-Origin Resource Sharing (CORS) is documented here: http://www.w3.org/TR/cors/ public void AddAccessControlHeaders(RavenBaseApiController controller, HttpResponseMessage msg) { if (string.IsNullOrEmpty(landlord.SystemConfiguration.AccessControlAllowOrigin)) return; controller.AddHeader("Access-Control-Allow-Credentials", "true", msg); bool originAllowed = landlord.SystemConfiguration.AccessControlAllowOrigin == "*" || landlord.SystemConfiguration.AccessControlAllowOrigin.Split(' ') .Any(o => o == controller.GetHeader("Origin")); if (originAllowed) { controller.AddHeader("Access-Control-Allow-Origin", controller.GetHeader("Origin"), msg); } controller.AddHeader("Access-Control-Max-Age", landlord.SystemConfiguration.AccessControlMaxAge, msg); controller.AddHeader("Access-Control-Allow-Methods", landlord.SystemConfiguration.AccessControlAllowMethods, msg); if (string.IsNullOrEmpty(landlord.SystemConfiguration.AccessControlRequestHeaders)) { // allow whatever headers are being requested var hdr = controller.GetHeader("Access-Control-Request-Headers"); // typically: "x-requested-with" if (hdr != null) controller.AddHeader("Access-Control-Allow-Headers", hdr, msg); } else { controller.AddHeader("Access-Control-Request-Headers", landlord.SystemConfiguration.AccessControlRequestHeaders, msg); } }
private void FinalizeRequestProcessing(RavenBaseApiController controller, HttpResponseMessage response, Stopwatch sw) { LogHttpRequestStatsParams logHttpRequestStatsParam = null; try { StringBuilder sb = null; if (controller.CustomRequestTraceInfo != null) { sb = new StringBuilder(); foreach (var action in controller.CustomRequestTraceInfo) { action(sb); sb.AppendLine(); } while (sb.Length > 0) { if (!char.IsWhiteSpace(sb[sb.Length - 1])) break; sb.Length--; } } logHttpRequestStatsParam = new LogHttpRequestStatsParams( sw, GetHeaders(controller.InnerHeaders), controller.InnerRequest.Method.Method, response != null ? (int) response.StatusCode : 500, controller.InnerRequest.RequestUri.PathAndQuery, sb != null ? sb.ToString() : null ); } catch (Exception e) { Logger.WarnException("Could not gather information to log request stats", e); } if (logHttpRequestStatsParam == null || sw == null) return; sw.Stop(); controller.MarkRequestDuration(sw.ElapsedMilliseconds); LogHttpRequestStats(logHttpRequestStatsParam, controller.TenantName); TraceRequest(logHttpRequestStatsParam, controller.TenantName); }
private bool TryAuthorizeSingleUseAuthToken(RavenBaseApiController controller, string token, out HttpResponseMessage msg) { if (controller.WasAlreadyAuthorizedUsingSingleAuthToken) { msg = controller.GetEmptyMessage(); return true; } object result; HttpStatusCode statusCode; IPrincipal user; var success = TryAuthorizeSingleUseAuthToken(token, controller.TenantName, out result, out statusCode, out user); controller.User = user; if (success == false) msg = controller.GetMessageWithObject(result, statusCode); else msg = controller.GetEmptyMessage(); controller.WasAlreadyAuthorizedUsingSingleAuthToken = success; return success; }
public bool TryAuthorize(RavenBaseApiController controller, bool hasApiKey, bool ignoreDbAccess, out HttpResponseMessage msg) { var isGetRequest = IsGetRequest(controller.InnerRequest.Method.Method, controller.InnerRequest.RequestUri.AbsolutePath); var allowUnauthenticatedUsers = // we need to auth even if we don't have to, for bundles that want the user Settings.AnonymousUserAccessMode == AnonymousUserAccessMode.All || Settings.AnonymousUserAccessMode == AnonymousUserAccessMode.Admin || Settings.AnonymousUserAccessMode == AnonymousUserAccessMode.Get && isGetRequest; var token = GetToken(controller); if (token == null) { if (allowUnauthenticatedUsers) { msg = controller.GetEmptyMessage(); return true; } msg = WriteAuthorizationChallenge(controller, hasApiKey ? 412 : 401, "invalid_request", "The access token is required"); return false; } AccessTokenBody tokenBody; if (!AccessToken.TryParseBody(Settings.OAuthTokenKey, token, out tokenBody)) { if (allowUnauthenticatedUsers) { msg = controller.GetEmptyMessage(); return true; } msg = WriteAuthorizationChallenge(controller, 401, "invalid_token", "The access token is invalid"); return false; } if (tokenBody.IsExpired()) { if (allowUnauthenticatedUsers) { msg = controller.GetEmptyMessage(); return true; } msg = WriteAuthorizationChallenge(controller, 401, "invalid_token", "The access token is expired"); return false; } var writeAccess = isGetRequest == false; if (!tokenBody.IsAuthorized(controller.TenantName, writeAccess)) { if (allowUnauthenticatedUsers || ignoreDbAccess) { msg = controller.GetEmptyMessage(); return true; } msg = WriteAuthorizationChallenge(controller, 403, "insufficient_scope", writeAccess ? "Not authorized for read/write access for tenant " + controller.TenantName : "Not authorized for tenant " + controller.TenantName); return false; } controller.User = new OAuthPrincipal(tokenBody, controller.TenantName); CurrentOperationContext.Headers.Value[Constants.RavenAuthenticatedUser] = tokenBody.UserId; CurrentOperationContext.User.Value = controller.User; msg = controller.GetEmptyMessage(); return true; }
private bool TryCreateUser(RavenBaseApiController controller, string databaseName, out Func<HttpResponseMessage> onRejectingRequest) { var invalidUser = (controller.User == null || controller.User.Identity.IsAuthenticated == false); if (invalidUser) { onRejectingRequest = () => { var msg = ProvideDebugAuthInfo(controller, new { Reason = "User is null or not authenticated" }); controller.AddHeader("Raven-Required-Auth", "Windows", msg); if (string.IsNullOrEmpty(controller.SystemConfiguration.OAuthTokenServer) == false) { controller.AddHeader("OAuth-Source", controller.SystemConfiguration.OAuthTokenServer, msg); } msg.StatusCode = HttpStatusCode.Unauthorized; return msg; }; return false; } var dbUsersIsAllowedAccessTo = requiredUsers .Where(data => controller.User.Identity.Name.Equals(data.Name, StringComparison.InvariantCultureIgnoreCase)) .SelectMany(source => source.Databases) .Concat(requiredGroups.Where(data => controller.User.IsInRole(data.Name)).SelectMany(x => x.Databases)) .ToList(); var user = UpdateUserPrincipal(controller, dbUsersIsAllowedAccessTo); onRejectingRequest = () => { var msg = ProvideDebugAuthInfo(controller, new { user.Identity.Name, user.AdminDatabases, user.ReadOnlyDatabases, user.ReadWriteDatabases, DatabaseName = databaseName }); msg.StatusCode = HttpStatusCode.Forbidden; throw new HttpResponseException(msg); }; return true; }
public async Task<HttpResponseMessage> HandleActualRequest(RavenBaseApiController controller, HttpControllerContext controllerContext, Func<Task<HttpResponseMessage>> action, Func<HttpException, HttpResponseMessage> onHttpException) { HttpResponseMessage response = null; cancellationToken.ThrowIfCancellationRequested(); Stopwatch sw = Stopwatch.StartNew(); try { Interlocked.Increment(ref concurrentRequests); if (controller.SetupRequestToProperDatabase(this)) { if (controller.ResourceConfiguration.RejectClientsMode && controllerContext.Request.Headers.Contains(Constants.RavenClientVersion)) { response = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable) { Content = new StringContent("This service is not accepting clients calls") }; } else { response = await action(); } } } catch (HttpException httpException) { response = onHttpException(httpException); } finally { Interlocked.Decrement(ref concurrentRequests); try { FinalizeRequestProcessing(controller, response, sw); } catch (Exception e) { var aggregateException = e as AggregateException; if (aggregateException != null) { e = aggregateException.ExtractSingleInnerException(); } Logger.ErrorException("Could not finalize request properly", e); } } return response; }
private static HttpResponseMessage ProvideDebugAuthInfo(RavenBaseApiController controller, object msg) { string debugAuth = controller.GetQueryStringValue("debug-auth"); if (debugAuth == null) return controller.GetEmptyMessage(); bool shouldProvideDebugAuthInformation; if (bool.TryParse(debugAuth, out shouldProvideDebugAuthInformation) && shouldProvideDebugAuthInformation) { return controller.GetMessageWithObject(msg); } return controller.GetEmptyMessage(); }
private void FinalizeRequestProcessing(RavenBaseApiController controller, HttpResponseMessage response, Stopwatch sw) { LogHttpRequestStatsParams logHttpRequestStatsParam = null; try { StringBuilder sb = null; if (controller.CustomRequestTraceInfo != null && controller.CustomRequestTraceInfo.Count > 0) { sb = new StringBuilder(); foreach (var action in controller.CustomRequestTraceInfo) { action(sb); sb.AppendLine(); } while (sb.Length > 0) { if (!char.IsWhiteSpace(sb[sb.Length - 1])) break; sb.Length--; } } logHttpRequestStatsParam = new LogHttpRequestStatsParams( sw, GetHeaders(controller.InnerHeaders), controller.InnerRequest.Method.Method, response != null ? (int)response.StatusCode : 500, controller.InnerRequest.RequestUri.ToString(), sb != null ? sb.ToString() : null, controller.InnerRequestsCount ); } catch (Exception e) { Logger.WarnException("Could not gather information to log request stats", e); } if (logHttpRequestStatsParam == null || sw == null) return; sw.Stop(); if (landlord.IsDatabaseLoaded(controller.TenantName ?? Constants.SystemDatabase)) { controller.MarkRequestDuration(sw.ElapsedMilliseconds); } var curReq = Interlocked.Increment(ref reqNum); LogHttpRequestStats(controller, logHttpRequestStatsParam, controller.TenantName, curReq); if (controller.IsInternalRequest == false) { TraceTraffic(controller, logHttpRequestStatsParam, controller.TenantName); } RememberRecentRequests(logHttpRequestStatsParam, controller.TenantName); }
private PrincipalWithDatabaseAccess UpdateUserPrincipal(RavenBaseApiController controller, List<ResourceAccess> databaseAccessLists) { var access = controller.User as PrincipalWithDatabaseAccess; if (access != null) return access; var user = new PrincipalWithDatabaseAccess((WindowsPrincipal)controller.User); foreach (var databaseAccess in databaseAccessLists) { if (databaseAccess.Admin) user.AdminDatabases.Add(databaseAccess.TenantId); else if (databaseAccess.ReadOnly) user.ReadOnlyDatabases.Add(databaseAccess.TenantId); else user.ReadWriteDatabases.Add(databaseAccess.TenantId); } controller.User = user; Thread.CurrentPrincipal = user; return user; }
private void LogHttpRequestStats(RavenBaseApiController controller, LogHttpRequestStatsParams logHttpRequestStatsParams, string databaseName, long curReq) { if (Logger.IsDebugEnabled == false) return; if (controller is StudioController || controller is HardRouteController || controller is SilverlightController) return; // we filter out requests for the UI because they fill the log with information // we probably don't care about them anyway. That said, we do output them if they take too // long. if (logHttpRequestStatsParams.Headers["Raven-Timer-Request"] == "true" && logHttpRequestStatsParams.Stopwatch.ElapsedMilliseconds <= 25) return; var message = string.Format(CultureInfo.InvariantCulture, "Request #{0,4:#,0}: {1,-7} - {2,5:#,0} ms - {5,-10} - {3} - {4}", curReq, logHttpRequestStatsParams.HttpMethod, logHttpRequestStatsParams.Stopwatch.ElapsedMilliseconds, logHttpRequestStatsParams.ResponseStatusCode, logHttpRequestStatsParams.RequestUri, databaseName); Logger.Debug(message); if (string.IsNullOrWhiteSpace(logHttpRequestStatsParams.CustomInfo) == false) { Logger.Debug(logHttpRequestStatsParams.CustomInfo); } }
private bool TryAuthorizeSingleUseAuthToken(RavenBaseApiController controller, string token, out HttpResponseMessage msg) { OneTimeToken value; if (singleUseAuthTokens.TryRemove(token, out value) == false) { msg = controller.GetMessageWithObject( new { Error = "Unknown single use token, maybe it was already used?" }, HttpStatusCode.Forbidden); return false; } if (string.Equals(value.DatabaseName, controller.TenantName, StringComparison.InvariantCultureIgnoreCase) == false && (value.DatabaseName == "<system>" && controller.TenantName == null) == false) { msg = controller.GetMessageWithObject( new { Error = "This single use token cannot be used for this database" }, HttpStatusCode.Forbidden); return false; } if ((SystemTime.UtcNow - value.GeneratedAt).TotalMinutes > 2.5) { msg = controller.GetMessageWithObject( new { Error = "This single use token has expired" }, HttpStatusCode.Forbidden); return false; } if (value.User != null) { CurrentOperationContext.Headers.Value[Constants.RavenAuthenticatedUser] = value.User.Identity.Name; } CurrentOperationContext.User.Value = value.User; controller.User = value.User; msg = controller.GetEmptyMessage(); return true; }