Liferay: create user through SOAP webservices - soap

I am trying to create a new user in Liferay using remote SOAP calls.
That's what I got so far (groovy):
public UserSoap addUser(UserInfo ui, String password) {
def companyPort = companyServiceLocator.getPortal_CompanyService(new URL(companyBaseUrl))
def userPort = serviceLocator.getPortal_UserService(new URL(userBaseUrl))
// FIXME: this should come from configuration as well
def company = companyPort.getCompanyByVirtualHost('localhost')
def sites = new WebsiteSoap[0]
def sc = new ServiceContext()
sc.indexingEnabled = true
sc.assetEntryVisible = true
sc.companyId = company.companyId
def newUs = userPort.addUser(company.getCompanyId(), false, password, password, false, ui.username, ui.email, 0, null, 'IT', ui.name, null, ui.surname, 0, 0, true, 1, 1, 1970, '', new long[0], new long[0], new long[0], new long[0], new AddressSoap[0], new EmailAddressSoap[0], new PhoneSoap[0], sites, new AnnouncementsDeliverySoap[0], false, sc)
userPort.updateStatus(newUs.userId, 0, sc)
userPort.updateAgreedToTermsOfUse(newUs.userId, true)
newUs
}
This get executed without errors, the user is available but is not associated to any site. How do I do it? Also calling three different service to insert a single user doesn't seem optimal.

Related

How do you modify the client and api scopes that are predefined in IdentityServer 7 when creating a web app with individual accounts?

I have been stuck on this problem for a few days now. I have a web application being built on .NET Core 7, IdentityServer 7, EntityFramework 7, and Angular 15 and written in C#. The scope in the JWT contains a scope of (MyAppAPI, openid, and profile). I am trying to find a way to add roles to the scope. I've tried several approaches, but all of them are directed towards creating new IdentityResources, Clients, and ApiScopes. This approach throws errors because they already exist from IdentityServer 7.
Hoping someone can help. Thanks.
My latest effort consisted of applying option arguments to the builder.Services.AddIdentityServer().AddApiAuthorization<ApplicationUser, ApplicationDbContext>() method in the Program.cs file. But I get an error saying "Can't determine the type for the client type". So I don't know if I'm close to getting this all resolved or am way off track.
Here are the contents of my Program.cs file:
using Duende.IdentityServer.AspNetIdentity;
using Duende.IdentityServer.EntityFramework.Entities;
using Duende.IdentityServer.Models;
using AdminPortal.Areas.Identity.Data;
using AdminPortal.Areas.Identity.Models;
using AdminPortal.Framework;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.AzureAppServices;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
var builder = WebApplication.CreateBuilder(args);
string envName = string.IsNullOrEmpty(builder.Configuration["configEnvName"]) ? "development" : builder.Configuration["configEnvName"].ToString();
builder.Configuration.AddJsonFile("appsettings.json").AddJsonFile($"appsettings.{envName}.json");
builder.Logging.AddAzureWebAppDiagnostics();
builder.Services.Configure<AzureFileLoggerOptions>(options =>
{
options.FileName = "AdminPortal-diagnostics-";
options.FileSizeLimit = 50 * 1024;
options.RetainedFileCountLimit = 5;
});
builder.Services.Configure<AzureBlobLoggerOptions>(options =>
{
options.BlobName = "log.txt";
});
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
options.IdentityResources = Config.IdentityResources;
options.Clients = Config.Clients;
options.ApiScopes = Config.ApiScopes;
})
.AddProfileService<ProfileService>();
builder.Services.AddAuthentication()
.AddIdentityServerJwt();
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
//builder.Services.AddScoped<IClaimsTransformation, ClaimsTransformer>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();
app.MapFallbackToFile("index.html"); ;
app.Run();
And here are the contents of Config.cs:
using Duende.IdentityServer.Models;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using System.Collections.Generic;
namespace AdminPortal.Framework
{
public static class Config
{
public static IdentityResourceCollection IdentityResources =>
new IdentityResourceCollection(
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
//new IdentityResources.Email(), // Can implement later if needed
//new IdentityResources.Phone(), // Can implement later if needed
//new IdentityResources.Address(), // Can implement later if needed
new IdentityResource("roles", "User roles", new List<string> { "role" })
});
public static ApiScopeCollection ApiScopes =>
new ApiScopeCollection(
new ApiScope[]
{
new ApiScope("AdminPortalAPI"),
new ApiScope("openid"),
new ApiScope("profile"),
new ApiScope("roles")
}
);
public static ClientCollection Clients =>
new ClientCollection(
new Client[]
{
new Client
{
ClientId = "AdminPortalAPI",
ClientName = "AdminPortal Credentials Client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenType = AccessTokenType.Jwt,
ClientSecrets = { new Secret("AdminPortal_client_secret".Sha256()) },
AllowedScopes =
{
"AdminPortalAPI"
}
},
new Client
{
ClientId = "AdminPortal",
ClientName = "AdminPortal SPA",
AllowedGrantTypes = GrantTypes.Code,
AccessTokenType = AccessTokenType.Jwt,
RequirePkce = true,
RequireClientSecret = false,
AllowedScopes = { "openid", "profile", "AdminPortalAPI", "roles" },
RedirectUris = { https://localhost:44463/auth-callback },
PostLogoutRedirectUris = { https://localhost:44463/ },
AllowedCorsOrigins = { https://localhost:44463 },
AllowOfflineAccess = true
}
}
);
}
}
I found the solution to my problem. So I'll report it here for anyone else that finds themselves struggling with this issue or similar.
If you create a new project in Visual Studio and tell it to include Individual Accounts, it will use IdentityServer to build out an authentication framework that will make it easy to manage user accounts and authenticate users. However, if you want to implement role-based security, you'll have to build it out manually because the preconfigured code only partially implements IdentityServer and is not designed to let you customize the scopes (reference link: https://github.com/dotnet/aspnetcore/issues/16939).
To resolve this issue, I found a great tutorial that helped me build out the authentication and authorization framework using IdentityServer for my Angular 15 .NET Core 7 web application. Here is the link to it: https://code-maze.com/angular-security-with-asp-net-core-identity/

Resolve Authentication problem JWT Bearer for .NET Core API

I use .NET Core 3.1 API and I would like to configure a JWT Bear Token.
I have configured a method which generates a token with a ValidateLifetime of one day.
Then, I put [authorize] on my usercontroller method and I tested all with Postman, by using my token and selecting "Bearer Token as Authorisation method "but I don't know why it doesn't work.
I filled out the token generated previously by my GenerateToken method, I tried to enter only the header part of the jwt token, the header/content/signature of the jwt token but it doesn't work.
Would someone have a video or tutorial explaining how to test the "Bear token" as authentication mode?
//example of generated token :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYXJ0aHVyIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiIxIiwibmJmIjoiMTYxMDI5OTAzMyIsImV4cCI6IjE2MTAzODU0MzMiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbiJ9.E9TnS62nv10gNH8U03OPhK_QrGLEotnS7yjHBvh4i0E
{
var claims = new List<Claim>{
new Claim(ClaimTypes.Name , UserNAME),
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(JwtRegisteredClaimNames.Nbf,new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Exp,new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds().ToString())
};
claims.Add(new Claim(ClaimTypes.Role, "Admin"));
var token = new JwtSecurityToken(
new JwtHeader(
new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SECRET_KEY)),
SecurityAlgorithms.HmacSha256
)),
new JwtPayload(claims));
var output = new
{
Accces_Token = new JwtSecurityTokenHandler().WriteToken(token),
UserName = UserNAME
};
return output;
}
//this is my authentication services
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = "jwtBearer";
options.DefaultChallengeScheme = "jwtBearer";
}).AddJwtBearer("jwtBearer", jwtoptions => {
jwtoptions.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = SIGNING_KEY,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5) };
});
//this is my IApplicationBuilder application configure : {
app.UseAuthentication();
app.UseAuthorization();```}
From the code of generating token, SIGNING_KEY is a string, but IssuerSigningKey is a type of SecurityKey. So in the configuration, it need to be changed.
jwtoptions.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SIGNING_KEY)),
};
The way you tested token may be not correct. After generating the token, you put it in the request header. Note the Bearer.

Identity Server 3 + ASP.NET Core 2.0 MVC app - Federated single sign-out not including a redirect to ADFS before ending session

My web app is client to an Identity Server 3 STS, which is federated with ADFS for the external IdP. Sign-in works great. Sign-out from the STS is fine. But I have never been able to get IdSrv3 to redirect to ADFS for sign-out prior to ending the IdSrv3 session and ultimately redirecting to the app.
If I understand correctly, I should be able to have ADFS post back to the RP (IdSrv3) after signing out, at which point IdSrv3
Read the docs:
https://identityserver.github.io/Documentation/docsv2/advanced/federated-post-logout-redirect.html
As well as much of the anthology of the GitHub issues surrounding this topic of federated single sign-out.
Tracing through IdSrv3 I never see an attempt to redirect to ADFS for sign-out, so I assume I'm missing configuration here.
Once complexity is that I'm running IdSrv3 however my client apps are ASP.NET Core 2.0 so many of the samples don't cleanly reconcile with the latest Microsoft identity client middleware.
On the IdSrv3, these are (I believe) the relevant configuration components:
Configuration of Additional Identity Providers:
var wsFed = new WsFederationAuthenticationOptions
{
Wtrealm = ConfigurationManager.AppSettings["Wtrealm"],
MetadataAddress = metaDataAddress,
AuthenticationType = "ADFS",
Caption = "ACME ADFS",
SignInAsAuthenticationType = signInAsType
};
The IdSrv3 middleware:
coreApp.UseIdentityServer(
new IdentityServerOptions
{
SiteName = "eFactoryPro Identity Server",
SigningCertificate = Cert.Load(),
Factory = factory,
RequireSsl = true,
AuthenticationOptions = new AuthenticationOptions
{
IdentityProviders = ConfigureAdditionalIdentityProviders,
EnablePostSignOutAutoRedirect = true,
EnableSignOutPrompt = false,
EnableAutoCallbackForFederatedSignout = true
},
LoggingOptions = new LoggingOptions
{
EnableHttpLogging = true,
EnableKatanaLogging = true,
//EnableWebApiDiagnostics = true,
//WebApiDiagnosticsIsVerbose = true
}
});
coreApp.Map("/signoutcallback", cleanup =>
{
cleanup.Run(async ctx =>
{
var state = ctx.Request.Cookies["state"];
await ctx.Environment.RenderLoggedOutViewAsync(state);
});
});
});
Now for the Client side, an ASP.NET Core 2.0 MVC application:
Update: See accepted answer - the redirect to IdP for sign-out should have been handled on the IdSrv3 side with respect to redirecting to the external IdP (ADFS)
public static void ConfigureAuth(this IServiceCollection services,
ITicketStore distributedStore,
Options.AuthenticationOptions authOptions)
{
services.AddDataProtection();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(8);
options.SlidingExpiration = true;
options.SessionStore = distributedStore;
})
.AddOpenIdConnect(options =>
{
options.Authority = authOptions.Authority;
options.ClientId = authOptions.ClientId;
options.ClientSecret = authOptions.ClientSecret;
options.ResponseType = "code id_token";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("roles");
options.Scope.Add("email");
options.Scope.Add("offline_access");
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProviderForSignOut = n =>
{
var idTokenHint = n.ProtocolMessage.IdTokenHint;
if (!string.IsNullOrEmpty(idTokenHint))
{
var sessionId = n.HttpContext?.Session?.Id;
var signOutRedirectUrl = n.ProtocolMessage.BuildRedirectUrl();
if (sessionId != null)
{
n.HttpContext.Response.Cookies.Append("state", sessionId);
}
n.HttpContext?.Session?.Clear();
n.Response.Redirect(signOutRedirectUrl);
}
return Task.FromResult(0);
}
};
});
}
From the documentation I should be passing the "sign out message id" into that 'state' cookie. However, this extension method doesn't work in ASP.NET Core 2.0 as we don't really have access to OwinContext anymore.
var signOutMessageId = n.OwinContext.Environment.GetSignOutMessageId();
I've even tried instantiating a new OwinContext(n.HttpContext) to get at the environment dictionary - however, the value that the "GetSignOutMessageId()" obtains has a key of "id" which I can't find in the Owin variables.
It seems this cookie is really just necessary to persist state through all of the redirects so that after the PostLogoutUri of my client application is hit, which is currently set to "https://myapp/signout-callback-oidc", the message id can be used to finish cleaning up the session.
I'm also confused as to what role the "EnableAutoCallbackForFederatedSignout = true" setting plays on the IdSrv3 configuration.
From this description and looking at the code it would apear that this just saves me from having to set the "WReply" parameters on the ADFS signout:
https://github.com/IdentityServer/IdentityServer3/issues/2613
I would expect that ADFS would redirect to:
"https://myIdSrv3/core/signoutcallback" automatically if this settings was 'true'.
If anyone has any guidance to share it is much appreciated.
It turns out I was conflating some of the concepts in IdSrv3 that describe Federated Single Sign-Out initiated by the External Idp as opposed to my use case - sign-out initiated by the IdSrv3 client app, cascading "up" to the external IdP.
The root cause of this problem was in my UserService implementation. There I had overriden the "AuthenticateExternalAsync()" method, but did not specify the external identity provider in the AuthenticateResult object.
Here is the corrected implementation:
public override Task AuthenticateExternalAsync(ExternalAuthenticationContext context)
{
...
context.AuthenticateResult = new AuthenticateResult(
user.Id,
user.UserName,
new List<Claim>(),
context.ExternalIdentity.Provider);
return Task.FromResult(0);
}
Once the External Idp was specified in my AuthenticateResult, I was able to handle the WsFederationAuthenticationNotifications.RedirectToIdentityProvider event.
For the sake of completeness, here is my code to handle federated sign-out (client intiatited) from ADFS vis WsFed. It is more or less straight from the IdSrv3 documentation:
Notifications = new WsFederationAuthenticationNotifications()
{
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.IsSignOutMessage)
{
var signOutMessageId = n.OwinContext.Environment.GetSignOutMessageId();
if (signOutMessageId != null)
{
n.OwinContext.Response.Cookies.Append("state", signOutMessageId);
}
var cleanUpUri =
$#"{n.Request.Scheme}://{n.Request.Host}{n.Request.PathBase}/external-signout-cleanup";
n.ProtocolMessage.Wreply = cleanUpUri;
}
return Task.FromResult(0);
}
}
And finally, my /external-signout-cleanup implementation:
coreApp.Map("/external-signout-cleanup", cleanup =>
{
cleanup.Run(async ctx =>
{
var state = ctx.Request.Cookies["state"];
await ctx.Environment.RenderLoggedOutViewAsync(state);
});
});

jwt token multi tenancy

I implemented in my ASP.NET Core project Web API user authorization via token. I create a token at each login and everything seems to work.
My problem is that the app will be multi-tenancy, so I will have many subdomains client-side, e.g.
(Client1.myapp.com, client2.myapp.com, client3.myapp.com)
Server-side my app that manages the bees will accept a parameter that will be the name of the tenant.
Some examples:
apimyapp.com/client1/api/generateToken
apimyapp.com/client2/api/generateToken
apimyapp.com/client3/api/generateToken
Now if I create the token from client1 and I put in a call apimyapp.com/client2/api/users (insert token generated by the client1 in the header, but the call is made for the client2), the token will be validated and the call gets authorized.
Instead, I wish that the token was valid only for the tenant from which it was generated.
In my startup.cs:
app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = _config["Tokens:Issuer"],
ValidAudience = _config["Tokens:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"])),
ValidateLifetime = true
}
});
and in my controller for generation token:
var userClaims = _userManagerRepository.GetClaims(user);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.GivenName, user.UserName),
new Claim(JwtRegisteredClaimNames.FamilyName, user.UserName),
new Claim(JwtRegisteredClaimNames.Email, user.Email)
}.Union(userClaims);
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _config["Tokens:Issuer"],
audience: _config["Tokens:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddMinutes(90),
signingCredentials: creds
);
You can add a list on keys, Audiences etc like this to the TokenValidationParameters
ValidAudiences = new List<string>
{
"AUDIENCE1",
"AUDIENCE2"
}

A task was canceled

I am trying to co-host identityserver3 and web api (for user management using Bearer tokens) in the same startup. However I get the following error:
A task was canceled.
It appears the task cancellation occurs on startup when trying to call http://identity_local/core/.well-known/openid-configuration (identity_local points to localhost).
My startup is as follows:
app.Map("/core", idsrvApp =>
{
var factory = new IdentityServerServiceFactory();
factory.UserService = new IdentityServer3.Core.Configuration.Registration<IUserService, UserService>();
factory.ScopeStore = new IdentityServer3.Core.Configuration.Registration<IScopeStore>(resolver => scopeStore);
var options = new IdentityServerOptions
{
SigningCertificate = Certificate.Load(),
IssuerUri = "http://identity_local/core",
PublicOrigin = "http://identity_local",
RequireSsl = false, //for now
Factory = factory,
};
idsrvApp.UseIdentityServer(options);
});
app.Map("/admin", adminApp =>
{
adminApp.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://identity_local/core",
IssuerName = "identity_local",
ValidationMode = ValidationMode.Local,
RequiredScopes = new[] { "api", "roles" }
});
adminApp.UseResourceAuthorization(new AuthorisationManager());
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
adminApp.UseCors(CorsOptions.AllowAll);
adminApp.UseWebApi(config);
});
Does anyone know if a) it is possible to have both in the same startup and b) if so, what have I done wrong or what can I do to remedy the above.
At startup time the UseIdentityServerBearerTokenAuthentication tries to contact the IdentityServer metatadata endpoint, but since the server is not yet running it can't connect, thus an error.
For this situation, there's a flag called DelayLoadMetadata to delay load the metadata until the first time it's needed: https://identityserver.github.io/Documentation/docsv2/consuming/options.html