I have trouble to remove user from group. I have no problem with adding a user. I do not recieve any error from myGroup.Members.Remove(user as DirectoryObject);. Is it a bug?
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
User user = (User)await client.Users.GetByObjectId(userID).ExecuteAsync();
IGroup myIGroup = await client.Groups.GetByObjectId(objectId).ExecuteAsync();
Group myGroup = (Group)myIGroup;
if (myGroup != null && user != null)
{
try
{
switch (myAction)
{
case "Delete":
myGroup.Members.Remove(user as DirectoryObject);
break;
case "Add":
myGroup.Members.Add(user as DirectoryObject);
break;
}
await myGroup.UpdateAsync();
Try to add Expand(x => x.Members) while getting group. I've tried the following:
public async Task<Result> RemoveFromGroup(string upn, string groupId)
{
try
{
var group = (AD.Group) await ADClient.Groups
.Where(x => x.ObjectId == groupId)
.Expand(x => x.Members)
.ExecuteSingleAsync();
var user = (AD.User) await ADClient.Users
.Where(x => x.UserPrincipalName == upn)
.ExecuteSingleAsync();
group.Members.Remove(user);
await group.UpdateAsync();
return Result.Ok();
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message, null, ex));
}
}
It works. The only difference - I get user by its principal name but it doesn't matter in that case.
Related
Do you know if there is way to implement global message handlers that can support commands like stop, bye, cancel, exit Virtual assistant bot ? I am trying to implement something like this.
I have a virtual assistant built already and it has couple of Skills or Skill Bots.
When user is in the multi turn conversation with a Skill, user should be able to exit out of skill by commands like stop, bye, cancel, exit.
I found old v3 doc but nothing for v4.
Check the documentations provided here Handling User Interruption They explain how to handle user interruption for SDK v4
Find below an example of how you can configure this in the Virtual Assistant.
In your MainDialog.cs
Add the following for your OnContinueDialogAsync: Keeping in mind that you can change and edit this as you see fit just be sure to check the OnInterruptDialogAsync result (status in this example) before you continue
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))
{
var status = await OnInterruptDialogAsync(innerDc, cancellationToken).ConfigureAwait(false);
if (status == InterruptionAction.Resume)
{
// Resume the waiting dialog after interruption
await innerDc.RepromptDialogAsync().ConfigureAwait(false);
return EndOfTurn;
}
else if (status == InterruptionAction.Waiting)
{
// Stack is already waiting for a response, shelve inner stack
return EndOfTurn;
}
else
{
var activity = innerDc.Context.Activity;
if (activity.IsStartActivity())
{
await OnStartAsync(innerDc).ConfigureAwait(false);
}
switch (activity.Type)
{
case ActivityTypes.Message:
{
// Note: This check is a workaround for adaptive card buttons that should map to an event (i.e. startOnboarding button in intro card)
if (activity.Value != null)
{
await OnEventAsync(innerDc).ConfigureAwait(false);
}
else
{
var result = await innerDc.ContinueDialogAsync().ConfigureAwait(false);
switch (result.Status)
{
case DialogTurnStatus.Empty:
{
await RouteAsync(innerDc).ConfigureAwait(false);
break;
}
case DialogTurnStatus.Complete:
{
// End active dialog
await innerDc.EndDialogAsync().ConfigureAwait(false);
break;
}
default:
{
break;
}
}
}
// If the active dialog was ended on this turn (either on single-turn dialog, or on continueDialogAsync) run CompleteAsync method.
if (innerDc.ActiveDialog == null)
{
await CompleteAsync(innerDc).ConfigureAwait(false);
}
break;
}
case ActivityTypes.Event:
{
//do something for event activity
break;
}
case ActivityTypes.Invoke:
{
// Used by Teams for Authentication scenarios.
break;
}
default:
{
await OnSystemMessageAsync(innerDc).ConfigureAwait(false);
break;
}
}
return EndOfTurn;
}
}
And override OnInterruptDialogAsync like below example:
This example includes LUIS but you can do whatever you want in OnInterruptDialogAsync
protected override async Task<InterruptionAction> OnInterruptDialogAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken))
{
var result = InterruptionAction.NoAction;
if (dc.Context.Activity.Type == ActivityTypes.Message && !string.IsNullOrEmpty(dc.Context.Activity.Text))
{
// get current activity locale
var localeConfig = _services.GetCognitiveModels();
// check general luis intent
localeConfig.LuisServices.TryGetValue("General", out var luisService);
if (luisService == null)
{
throw new Exception("The specified LUIS Model could not be found in your Skill configuration.");
}
else
{
var luisResult = await luisService.RecognizeAsync<GeneralLuis>(dc.Context, cancellationToken);
var topIntent = luisResult.TopIntent();
if (topIntent.score > 0.5)
{
switch (topIntent.intent)
{
case GeneralLuis.Intent.Cancel:
{
result = await OnCancel(dc);
break;
}
case GeneralLuis.Intent.Help:
{
result = await OnHelp(dc);
break;
}
case GeneralLuis.Intent.Logout:
{
result = await OnLogout(dc);
break;
}
}
}
}
}
return result;
}
I've done migration of my app to aspnetcore and now I do have random issue with validation tokens.
1. Issue is that randomly users receive
An error was encountered while handling the remote login. Correlation
failed.
The problem is that if I go and test it my self it works.
Second problem is that when user receive Email confirmation token and click link from email they will get
invalid token
so they can't confirm email.
Firstly I thought issue is with UseCookiePolicy but I've disabled it.
Startup.cs
namespace Flymark.Online.Web
{
public class Startup
{
private readonly IHostingEnvironment _env;
public Startup(IHostingEnvironment env)
{
_env = env;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Configure SnapshotCollector from application settings
services.Configure<SnapshotCollectorConfiguration>(
Configuration.GetSection(nameof(SnapshotCollectorConfiguration)));
// Add SnapshotCollector telemetry processor.
services.AddSingleton<ITelemetryProcessorFactory>(sp => new SnapshotCollectorTelemetryProcessorFactory(sp));
services.AddApplicationInsightsTelemetryProcessor<TelemetryFilter>();
services.AddSingleton<ITelemetryInitializer, AppInsightsInitializer>();
services.AddCors();
var decompressionOptions = new RequestDecompressionOptions();
decompressionOptions.UseDefaults();
services.AddRequestDecompression(decompressionOptions);
FlymarkAppSettings.Init(Configuration, _env.EnvironmentName);
var storageUri = new Uri(Configuration.GetValue<string>("Flymark:DataProtectionStorageUrl"));
//Get a reference to a container to use for the sample code, and create it if it does not exist.
var container = new CloudBlobClient(storageUri).GetContainerReference("data-protection");
services.AddDataProtection()
.SetApplicationName("Flymark.Online")
.PersistKeysToAzureBlobStorage(container, "data-protection.xml");
services.AddDetection();
services.AddAutoMapper();
services.AddWebMarkupMin(
options =>
{
options.AllowMinificationInDevelopmentEnvironment = true;
options.AllowCompressionInDevelopmentEnvironment = true;
})
.AddHtmlMinification(o =>
{
o.ExcludedPages = new List<IUrlMatcher>
{
new WildcardUrlMatcher("/scripts/*")
};
o.MinificationSettings.AttributeQuotesRemovalMode = HtmlAttributeQuotesRemovalMode.KeepQuotes;
o.MinificationSettings.EmptyTagRenderMode = HtmlEmptyTagRenderMode.NoSlash;
o.MinificationSettings.RemoveOptionalEndTags = false;
})
.AddXmlMinification()
.AddHttpCompression();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Lax;
});
services
.AddScoped<UserStore<ApplicationUser, IdentityRole<int>, FlymarkContext, int, IdentityUserClaim<int>,
IdentityUserRole<int>, IdentityUserLogin<int>, IdentityUserToken<int>, IdentityRoleClaim<int>>,
ApplicationUserStore>();
services.AddScoped<UserManager<ApplicationUser>, FlymarkUserManager>();
services.AddScoped<RoleManager<IdentityRole<int>>, ApplicationRoleManager>();
services.AddScoped<SignInManager<ApplicationUser>, ApplicationSignInManager>();
services
.AddScoped<RoleStore<IdentityRole<int>, FlymarkContext, int, IdentityUserRole<int>,
IdentityRoleClaim<int>>, ApplicationRoleStore>();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddIdentity<ApplicationUser, IdentityRole<int>>(
o =>
{
o.User.RequireUniqueEmail = true;
})
.AddUserStore<ApplicationUserStore>()
.AddUserManager<FlymarkUserManager>()
.AddRoleStore<ApplicationRoleStore>()
.AddRoleManager<ApplicationRoleManager>()
.AddSignInManager<ApplicationSignInManager>()
.AddClaimsPrincipalFactory<FlymarkClaimsPrincipalFactory>()
.AddDefaultTokenProviders();
services.AddSingleton<ILoggerFactory, LoggerFactory>(sp =>
new LoggerFactory(
sp.GetRequiredService<IEnumerable<ILoggerProvider>>(),
sp.GetRequiredService<IOptionsMonitor<LoggerFilterOptions>>()
)
);
services.Configure<ApiBehaviorOptions>(options => { options.SuppressModelStateInvalidFilter = true; });
services.AddMemoryCache();
services.AddSingleton<IEmailSender, FlymarkEmailSender>();
services.AddMvc(o =>
{
o.Conventions.Add(new FlymarkAsyncConvention());
o.AllowValidatingTopLevelNodes = false;
o.AllowEmptyInputInBodyModelBinding = true;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(opt =>
{
opt.SerializerSettings.DateFormatString = "dd/MM/yyyy";
opt.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
var resolver = opt.SerializerSettings.ContractResolver;
if (resolver == null) return;
if (resolver is DefaultContractResolver res) res.NamingStrategy = null;
});
services.Configure<IdentityOptions>(options =>
{
// Default Password settings.
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
options.Lockout.MaxFailedAccessAttempts = 20;
});
services
.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(OAuthValidationDefaults.AuthenticationScheme,
IdentityConstants.ApplicationScheme)
.RequireAuthenticatedUser()
.Build();
});
services.AddAuthentication()
.AddExternalAuthProviders(Configuration)
.AddFlymarkOpenIdConnectServer()
.AddOAuthValidation(OAuthValidationDefaults.AuthenticationScheme);
services.Configure<SecurityStampValidatorOptions>(options =>
{
// This is the key to control how often validation takes place
options.ValidationInterval = TimeSpan.FromMinutes(15);
});
services.ConfigureApplicationCookie(config =>
{
config.LoginPath = "/Identity/Account/LogIn";
config.AccessDeniedPath = "/Identity/Account/LogIn";
config.SlidingExpiration = true;
config.Events.OnRedirectToLogin = OnRedirectToLoginAsync;
});
}
private Task OnRedirectToLoginAsync(RedirectContext<CookieAuthenticationOptions> context)
{
if (context.HttpContext.Request.Path.Value.Contains("/api"))
context.Response.StatusCode = 401;
else
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
}
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource());
//builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterModule(new FlymarkDalDiModule
{
Configuration = Configuration
});
builder.RegisterModule(new DbDiModule(FlymarkAppSettings.Instance.DbContextConnection,
FlymarkAppSettings.Instance.StorageConnectionString));
builder.RegisterModule<FlymarkWebDiModule>();
}
private CultureInfo CreateCulture(string key)
{
return new CultureInfo(key)
{
NumberFormat = {NumberDecimalSeparator = "."},
DateTimeFormat = {ShortDatePattern = "dd/MM/yyyy"}
};
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory, IMapper mapper)
{
#if DEBUG
mapper.ConfigurationProvider.AssertConfigurationIsValid();
#endif
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = context =>
{
context.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store");
context.Context.Response.Headers.Add("Expires", "-1");
}
});
}
else
{
app.UseExceptionHandler("/Error/Error500");
app.UseStaticFiles();
}
app.UseCors(builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowCredentials()
.SetPreflightMaxAge(TimeSpan.FromMinutes(5))
.AllowAnyHeader();
});
app.UseRequestDecompression();
app.UseLegacyTokenContentTypeFixMiddleware();
var supportedCultures = new[]
{
CreateCulture("en"),
CreateCulture("ru"),
CreateCulture("uk")
};
app.UseFlymarkExceptionMiddleware();
app.UseCookiePolicy();
app
.UseAuthentication()
.UseDomainMiddleware()
.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
})
.UseWebMarkupMin();
app.Use(async (ctx, next) =>
{
await next();
if (ctx.Response.StatusCode == 404 && !ctx.Response.HasStarted)
{
//Re-execute the request so the user gets the error page
var originalPath = ctx.Request.Path.Value;
ctx.Items["originalPath"] = originalPath;
ctx.Request.Path = "/error/error404";
await next();
}
});
app
.UseMvc(routes =>
{
routes.MapRoute(
"areaRoute",
"{area:exists}/{controller=Dashboard}/{action=Index}/{id?}");
routes.MapRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
}
}
}
I am generating url for email confirmation like this:
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page("/Account/ConfirmEmail",
null,
new {userId = user.Id, code = code.ToBase64String()},
returnDomainUrl.Scheme,
returnDomainUrl.Host);
I also thought that it could be an angularjs (I still have it on my page) but its not loaded at /signin-facebook since its handled by middleware.
I think issue is somehwere with dataprotection since I am getting them in login and confirmation email
I also tried to base 64 email token, but it wont help in addition I think that url is encoded automatically by Page.Url
Finally after weeks of investigations I found an issue.
When user register I will send email and sms, then user goes and confirm sms, which will trigger update of security stamp. Then later if user click confirm email and it fails because security stamp is not the same as in a token
So moving sending confirmation email after phone number is confirmed. Solved half of my problem.
Most probably the tokens validation failed because the tokens are generated in a domain and being validated in another domain.
In ASP.Net this can be solved by having the same machineKey in both domains web.config files.
For ASP.Net Core you can replace the machineKey as described here so you have the same cryptographic settings in both of domains.
see: Replace the ASP.NET machineKey in ASP.NET Core
We have an issue that occurs at every method call for limited periods of time. Then it works as expected. The issue is that the code produces double WHERE clauses.
We're using Servicestack 4.5.14
The method we have:
protected static void InsertOrUpdate<T>(
IDbConnection connection,
T item,
Expression<Func<T, bool>> singleItemPredicate,
Expression<Func<T, object>> updateOnlyFields = null)
{
var type = item.GetType();
var idProperty = type.GetProperty("Id");
if (idProperty == null)
{
throw new Exception("Cannot insert or update on a class with no ID property");
}
var currentId = (int)idProperty.GetValue(item);
if (currentId != 0)
{
throw new Exception("Cannot insert or update with non-zero ID");
}
var query = connection.From<T>().Where(singleItemPredicate).WithSqlFilter(WithUpdateLock);
T existingItem;
try
{
existingItem = connection.Select(query).SingleOrDefault();
Log.Verbose(connection.GetLastSql);
}
catch (SqlException)
{
Log.Verbose(connection.GetLastSql);
throw;
}
if (existingItem == null)
{
Insert(connection, item);
return;
}
var existingId = (int)idProperty.GetValue(existingItem);
idProperty.SetValue(item, existingId);
try
{
var affectedRowCount = connection.UpdateOnly(item, onlyFields: updateOnlyFields, where: singleItemPredicate);
Log.Verbose(connection.GetLastSql);
if (affectedRowCount != 1)
{
throw new SwToolsException("Update failed");
}
}
catch (SqlException)
{
Log.Verbose(connection.GetLastSql);
throw;
}
}
When it all works, an example output from the logs could be:
SELECT "Id", "Application", "Hostname", "LastContact", "Version", "ToolState", "ServerState"
FROM "ca"."ExecutionHost"
WITH (UPDLOCK) WHERE ("Hostname" = #0)
UPDATE "ca"."ExecutionHost" SET "LastContact"=#LastContact, "Version"=#Version, "ToolState"=#ToolState, "ServerState"=#ServerState WHERE ("Hostname" = #0)
When it fails, the output (same session, only seconds later) was:
SELECT "Id", "Application", "Hostname", "LastContact", "Version", "ToolState", "ServerState"
FROM "ca"."ExecutionHost"
WITH (UPDLOCK) WHERE ("Hostname" = #0)
UPDATE "ca"."ExecutionHost" SET "LastContact"=#LastContact, "Version"=#Version, "ToolState"=#ToolState, "ServerState"=#ServerState WHERE "LastContact"=#LastContact, "Version"=#Version, "ToolState"=#ToolState, "ServerState"=#ServerState WHERE ("Hostname" = #0)
Marked in bold is the addition to the SQL that makes the call to fail. It seems that it adds an additional WHERE clause with the content from the SET clause.
We've been debugging this for a while and don't really know if the issue is on "our" side or in Servicestack.
Any ideas on where to continue?
I am using following method to sync Azure DB local table with server table but the changes which I made on my local DB are not reflecting to the Azure server,
public async Task PushDataAsync()
{
try
{
await _mobileService.SyncContext.PushAsync();
}
catch (Exception exc)
{
throw exc;
}
}
While using above method I am getting Error :-
Push Operation Fail.
Any Help will appreciated.
you are using right method to sync your offline store with server which is :-
await _mobileService.SyncContext.PushAsync();
I would suggest you to wrote few line of code in catch block which will help you to find out the reasons why the operations are not performed on server side
please use code bellow in catch block:-
public async Task PushDataAsync()
{
try
{
await _mobileService.SyncContext.PushAsync();
}
catch (MobileServicePushFailedException exc)
{
if (exc.PushResult != null)
{
syncErrors = exc.PushResult.Errors;
}
}
// Simple error/conflict handling.
if (syncErrors != null)
{
foreach (var error in syncErrors)
{
if (error.OperationKind == MobileServiceTableOperationKind.Update && error.Result != null || error.OperationKind == MobileServiceTableOperationKind.Insert && error.Result != null || error.OperationKind == MobileServiceTableOperationKind.Delete && error.Result != null)
{
//Update failed, reverting to server's copy.
await error.CancelAndUpdateItemAsync(error.Result);
}
else
{
// Discard local change.
await error.CancelAndDiscardItemAsync();
}
Debug.WriteLine(#"Error executing sync {2} operation. Item: {0} ({1}). Operation discarded.",
error.TableName, error.Item["id"], error.OperationKind);
}
}
}
Remember that PushAsync() pushes ALL changes from your local store to the cloud and that PullAsync first performs a Push. I would get rid of the service variable for each table and just use the service as a singleton class across your app. Here is my initialization. After this method returns, my local db is synced with the cloud and I can start using my tables:
public async Task InitializeStoreAsync()
{
try
{
var sqliteStore = _platform.MobileServiceSqliteStore;
sqliteStore.DefineTable<Memory>();
sqliteStore.DefineTable<User> ();
sqliteStore.DefineTable<Comment> ();
sqliteStore.DefineTable<Status>();
await _zumoClient.SyncContext.InitializeAsync(sqliteStore);
_memoryTable = _zumoClient.GetSyncTable<Memory> ();
_userTable = _zumoClient.GetSyncTable<User> ();
_commentTable = _zumoClient.GetSyncTable<Comment> ();
_statusTable = _zumoClient.GetSyncTable<Status>();
await _userTable.PullAsync ();
await _memoryTable.PullAsync ();
await _commentTable.PullAsync ();
await _statusTable.PullAsync();
}
catch (Exception ex)
{
Debug.WriteLine ("Initialize Store failed: {0}", ex.Message);
}
}
https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter3/client/ search for "Handling Conflict Resolution"
I'm struggling with the SharePoint 2007 AfterProperties. I've a people input field, where several people can be added.
On the ItemUpdating event I now need to determine which users were added, removed or stayed the same.
Unfortunately this becomes quit difficult, as the id of the untouched users turns to -1 in the AfterProperties, so that I cant not use SPFieldUserValueCollection to find the user.
An example. properties.ListItem["AssignedTo"].ToString() shows:
1;#domain\user1;#2;#domain\user2
properties.AfterProperties["AssignedTo"].ToString() shows:
-1;#domain\user1;#-1;#domain\user2;#3;#domain\user3 <-Added a user
I planned to use following code, to determine removed and added users:
foreach (SPFieldUserValue oldUser in oldUserCollection)
{
if (newUserCollection.Find(x => x.LookupId == oldUser.LookupId) == null)
{
RemoveRole(aListItem, oldUser.User, roleDefCollection[workerRoleName]);
}
}
foreach (SPFieldUserValue newUser in newUserCollection)
{
if(oldUserCollection.Find(x => x.User.LoginName == newUser.LookupValue) == null)
{
AddRole(aListItem, newUser.User, roleDefCollection[workerRoleName]);
}
}
How can I archive, that the AfterProperties show the right lookupid?
Solved the problem by myself. Instead of using the SPFieldUserCollection I'm now using a list and try to parse all the information by myself out of the string.
Regex reg = new Regex(#"\;\#");
string[] usernameParts = reg.Split(usernames);
List<SPUser> list = new List<SPUser>();
int id;
foreach (string s in usernameParts)
{
if (!string.IsNullOrEmpty(s))
{
if (!Int32.TryParse(s, out id))
{
if (list.Find(x => x.ID == spweb.Users[s].ID) == null)
list.Add(spweb.Users[s]);
}
else
{
if (Convert.ToInt32(s) != -1)
{
if (list.Find(x => x.ID == Convert.ToInt32(s)) == null)
list.Add(spweb.Users.GetByID(Convert.ToInt32(s)));
}
}
}
}