Unable to login Portal community after activating Critical update - salesforce

Before I activated the critical update name 'Restrict Access to #AuraEnabled Apex Methods for Guest and Portal Users Based on User Profile' I can login to Portal community properly.
After I Activated that critical update, community user can not login to Portal and the error message in response of Network tab will be 'You do not have access to the Apex class named LightningLoginFormController
I am sure that guest or portal user profile can access LightningLoginFormController Apex class.
global class LightningLoginFormController {
public LightningLoginFormController() {
}
#AuraEnabled
public static String login(String username, String password, String startUrl) {
try{
ApexPages.PageReference lgn = Site.login(username, password, startUrl);
aura.redirect(lgn);
return null;
}
catch (Exception ex) {
return ex.getMessage();
}
}
#AuraEnabled
public static Boolean getIsUsernamePasswordEnabled() {
Auth.AuthConfiguration authConfig = getAuthConfig();
return authConfig.getUsernamePasswordEnabled();
}
#AuraEnabled
public static Boolean getIsSelfRegistrationEnabled() {
Auth.AuthConfiguration authConfig = getAuthConfig();
return authConfig.getSelfRegistrationEnabled();
}
#AuraEnabled
public static String getSelfRegistrationUrl() {
Auth.AuthConfiguration authConfig = getAuthConfig();
if (authConfig.getSelfRegistrationEnabled()) {
return authConfig.getSelfRegistrationUrl();
}
return null;
}
#AuraEnabled
public static String getForgotPasswordUrl() {
Auth.AuthConfiguration authConfig = getAuthConfig();
return authConfig.getForgotPasswordUrl();
}
#TestVisible
private static Auth.AuthConfiguration getAuthConfig(){
Id networkId = Network.getNetworkId();
Auth.AuthConfiguration authConfig = new Auth.AuthConfiguration(networkId,'');
return authConfig;
}
#AuraEnabled
global static String setExperienceId(String expId) {
// Return null if there is no error, else it will return the error message
try {
if (expId != null) {
Site.setExperienceId(expId);
}
return null;
} catch (Exception ex) {
return ex.getMessage();
}
}
}
Does someone else face the same problem?

You need to explicitly allow access to Apex classes containing #AuraEnabled methods.
Guest, portal, or community user can access an #AuraEnabled Apex method only when the user’s profile or an assigned permission set allows access to the Apex class.
More details here:
https://releasenotes.docs.salesforce.com/en-us/spring20/release-notes/rn_lc_restrict_apex_guest_users.htm

Related

Unable to login Experience cloud site

Whenever I'm trying to login, I'm getting this error 'You do not have access to the Apex class named LightningLoginFormController'. I've made changes in Apex Class Security and added all the profiles, also checked by using 'with sharing' and 'without sharing', but it's still not working.
global class LightningLoginFormController {
public LightningLoginFormController() {
}
#AuraEnabled
public static String login(String username, String password, String startUrl) {
try{
ApexPages.PageReference lgn = Site.login(username, password, startUrl);
aura.redirect(lgn);
return null;
}
catch (Exception ex) {
return ex.getMessage();
}
}
#AuraEnabled
public static Boolean getIsUsernamePasswordEnabled() {
Auth.AuthConfiguration authConfig = getAuthConfig();
return authConfig.getUsernamePasswordEnabled();
}
#AuraEnabled
public static Boolean getIsSelfRegistrationEnabled() {
Auth.AuthConfiguration authConfig = getAuthConfig();
return authConfig.getSelfRegistrationEnabled();
}
#AuraEnabled
public static String getSelfRegistrationUrl() {
Auth.AuthConfiguration authConfig = getAuthConfig();
if (authConfig.getSelfRegistrationEnabled()) {
return authConfig.getSelfRegistrationUrl();
}
return null;
}
#AuraEnabled
public static String getForgotPasswordUrl() {
Auth.AuthConfiguration authConfig = getAuthConfig();
return authConfig.getForgotPasswordUrl();
}
#TestVisible
private static Auth.AuthConfiguration getAuthConfig(){
Id networkId = Network.getNetworkId();
Auth.AuthConfiguration authConfig = new Auth.AuthConfiguration(networkId,'');
return authConfig;
}
#AuraEnabled
global static String setExperienceId(String expId) {
// Return null if there is no error, else it will return the error message
try {
if (expId != null) {
Site.setExperienceId(expId);
}
return null;
} catch (Exception ex) {
return ex.getMessage();
}
}
}
For this to work, you need to add the Apex Class in Community Portal User Profile i.e. the Profile using "Guest User License".
Navigation: All sites --> Workspaces --> Administration --> Pages --> Go to Force.com --> Public Access Settings --> Enabled Apex Class Access.
And if you have a custom function in your Apex Class that is using SOQL query, for that to fetch data you will need to use "without sharing" security settings!!

DaoAuthenticationProvider not getting invoked

I am trying to authenticate a request from using spring security, I have followed few blogs and videos but i am not able to fix the issues.
Security configuration is loaded but my requests are not getting authenticated. I get 403 error
Bean class in WebSecurityConfigureAdapter
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userService);
System.out.println(userDetailsService());
return authProvider;
}
over riding configure method
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/products","/orders").hasAnyRole("admin");//.authenticated();
}
user details services overridding loadUserByUsername
#Override
public UserDetails loadUserByUsername(String loginName) {
String authenticated = "false";
UserDetails userDetails = null;
List<Users> usersList = usersRepository.findByLoginName(loginName);
if(usersList.size()==0) {
return null;
}else {
for (Users users : usersList) {
List<Roles> rolesList = users.getRoles();
for (Roles roles : rolesList) {
if(roles.getUserRole().equalsIgnoreCase("admin")) {
authenticated = "admin";
GrantedAuthority authority = new SimpleGrantedAuthority(authenticated);
User user = new User(users.getLoginName(),users.getPassword(),Arrays.asList(authority));
userDetails = (UserDetails)user;
}
}
}
return userDetails;
}
using postman setting up the credentials as basicAuth, its throwing 403 error
the issue was...
we need to either set the value for different getters in userdetails class or explicitly set to true
i have below getters explicitly set it to true, it worked for me
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}

Secure Sign in based on type of employment in ADF

I have to create an ADF application in which there is one login page. The login page has two input fields for username and password , one radio button for the type of employee (i.e either employee or manager) and one submit button.
Now depending on the type of user he/she is, one should get redirected to employee or manager page.
For credentials I am using ADF security feature. I have created two users, one for manager role and one for employee role.
I am using Java class for sign in :-
package demo.view;
import java.io.IOException;
import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class Login {
private String _username;
private String _password;
private String typeOfEmpl;
public void setUsername(String _username) {
this._username = _username;
}
public String getUsername() {
return _username;
}
public void setPassword(String _password) {
this._password = _password;
}
public String getPassword() {
return _password;
}
public void setTypeOfEmpl(String typeOfEmpl) {
this.typeOfEmpl = typeOfEmpl;
}
public String getTypeOfEmpl() {
return typeOfEmpl;
}
public String doLogin() {
FacesContext ctx = FacesContext.getCurrentInstance();
if (_username == null || _password == null) {
showError("Invalid credentials", "An incorrect username or password was specified.", null);
} else {
ExternalContext ectx = ctx.getExternalContext();
HttpServletRequest request = (HttpServletRequest) ctx.getExternalContext().getRequest();
try {
request.login(_username, _password);
_username = null;
_password = null;
HttpSession session = request.getSession();
session.setAttribute("success_url", "/faces" + ctx.getViewRoot().getViewId());
redirect(ectx.getRequestContextPath() + "/adfAuthentication");
} catch (ServletException fle) {
showError("ServletException", "Login failed. Please verify the username and password and try again.",
null);
}
}
return null;
}
private void redirect(String forwardUrl) {
FacesContext ctx = FacesContext.getCurrentInstance();
ExternalContext ectx = ctx.getExternalContext();
try {
ectx.redirect(forwardUrl);
} catch (IOException ie) {
showError("IOException", "An error occured during redirecting. Please consult logs for more info.", ie);
}
}
private void showError(String errType, String message, Exception e) {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, errType, message);
FacesContext.getCurrentInstance().addMessage("d2:it35", msg);
if (e != null) {
e.printStackTrace();
}
}
}
This code allows access to only those people who are privileged to the application. But I am unable to relate the radio selection for employee or manager and move to their pages respectively.
How can I correlate both the information(i.e secure sign in and the page that should open on the basis of their employment) and move forward with my application.
Try using a taskflow router as the start of your page flow to look at the value of the user role and based on it direct to the correct page.
Use EL to access the role used.
More info - http://www.oracle.com/technetwork/issue-archive/2012/12-jan/o12adf-1364748.html

EJB: how to check user is authenticated

Use logged throuth the JSP form:
#ManagedBean
#SessionScoped
public class LoginView {
private String username; //+getter +setter
private String password; //+getter +setter
public String submit()
{
try {
HttpServletRequest request = (HttpServletRequest) FacesContext
.getCurrentInstance()
.getExternalContext()
.getRequest();
request.login(username, password);
} catch (ServletException e) {
FacesContext.getCurrentInstance().addMessage("login-form:username",
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Validation Error", "Incorrect login or password"));
return "/login";
}
return "/index?faces-redirect=true";
}
}
How to check user logged in thorug EJB?
Below example whan I need:
#Stateless
public class SessionServiceBean {
#Resource
SessionContext sessionContext;
#EJB
UserService userService;
#Produces
#Named
#LoggedIn
public User getLoggedUser() {
if (/* check user is logged */) {
return userService.getByName(sessionContext.getCallerPrincipal().getName());
}
}
}
I foun only that the not logged use has name "anonymous", but it not better way, I think.
You are using a #Stateless EJB. In a Stateless EJB, you are working with user sessions. This is probably not right and the bean should be #Stateful.
I see no point in using EJBs in your example. Using pure CDI bean annotated with #SessionScoped would be sufficient.
You can store you user's session information directly in a session scoped bean. There is not need to use HttpServletRequest. For example:
#Named
#SessionScoped
public class UserSession implements Serializable {
private User activeUser;
public void logIn(User user) {
this.activeUser = user;
}
public void logOut() {
activeUser = null;
}
public boolean isLoggedIn() {
return activeUser != null;
}
public User getActiveUser() {
return activeUser;
}
}

Does new ASP.NET MVC identity framework work without Entity Framework and SQL Server?

I am new to ASP.NET MVC 5 and so I am trying to use it as much as possible to learn it by practice.
So I am thinking of using the new OWIN implementation of ASP.NET MVC to implement the authentication and authorization of my project. That said, I am building the project in a way that it can work with various types of databases.
So far I have used generic ADO.NET elements (e.g. DbDataReader etc) and I have refused to use any ORM. So I am wondering if I can go ahead with using the new identity system of ASP.NET or will I be bound to Entity Framework and SQL Server if I do so?
Not that simple. Not that hard either.
You'll have to write your custom implementation of:
IUserStore<TUser>
IUserPasswordStore<TUser>
IUserTwoFactorStore<TUser>
IUserClaimStore<TUser>
IRoleStore<TRole>
IUserSecurityStampStore<TUser, string>
IUserRoleStore<TUser, string>
UserManager<TUser>
Then create your own user implementation, from IUser<TKey>, like:
public class MyUser : IUser<string>
{
public string Id { get; set; }
public string UserName { get; set; }
}
Finally, from NuGet, remove AspNet.Identity.EntityFramework, which will remove EntityFramework too if you're not using it elsewhere.
Wherever your code breaks, rewrite it to use your custom implementations.
Tip
Create a MyUserRepository which implements items from 1 to 7.
Then, create a MyUserManager which implements item 8.
It will be damn easy to wire that up in place of default AspNet.Identity.EntityFramework classes.
To piggy-back on what ALMMa said, when I was working on my own custom implementation, I found this article to be invaluable:
Overview of Custom Storage Providers for ASP.NET Identity
It details no only what Interfaces need to be implemented, but goes into detail on how to implement them and gives code sample references to an actual MySQL implementation.
You just need to override some classes in the following manner to get basic role based authentication working without Entity Framework and SQL..
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
}
public class ApplicationUser : IUser
{
public ApplicationUser()
{
Id = Guid.NewGuid().ToString();
Roles = new List<string>();
}
public virtual string Email { get; set; }
public List<string> Roles { get; set; }
public virtual string Password { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public string Id { get; }
public string UserName { get; set; }
public virtual void AddRole(string role)
{
Roles.Add(role);
}
public virtual void RemoveRole(string role)
{
Roles.Remove(role);
}
}
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options,
IOwinContext context)
{
var manager =
new ApplicationUserManager(
new UserStoreService<ApplicationUser>(context.Get<ApplicationDbContext>().Users));
manager.PasswordHasher = new FusionPasswordHasher();
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = false,
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
return manager;
}
public virtual async Task<IdentityResult> AddUserToRolesAsync(string userId, IList<string> roles)
{
var userRoleStore = (IUserRoleStore<ApplicationUser, string>) Store;
var user = await FindByIdAsync(userId).ConfigureAwait(false);
if (user == null)
throw new InvalidOperationException("Invalid user Id");
var userRoles = await userRoleStore.GetRolesAsync(user).ConfigureAwait(false);
// Add user to each role using UserRoleStore
foreach (var role in roles.Where(role => !userRoles.Contains(role)))
await userRoleStore.AddToRoleAsync(user, role).ConfigureAwait(false);
// Call update once when all roles are added
return await UpdateAsync(user).ConfigureAwait(false);
}
public virtual async Task<IdentityResult> RemoveUserFromRolesAsync(string userId, IList<string> roles)
{
var userRoleStore = (IUserRoleStore<ApplicationUser, string>) Store;
var user = await FindByIdAsync(userId).ConfigureAwait(false);
if (user == null)
throw new InvalidOperationException("Invalid user Id");
var userRoles = await userRoleStore.GetRolesAsync(user).ConfigureAwait(false);
// Remove user to each role using UserRoleStore
foreach (var role in roles.Where(userRoles.Contains))
await userRoleStore.RemoveFromRoleAsync(user, role).ConfigureAwait(false);
// Call update once when all roles are removed
return await UpdateAsync(user).ConfigureAwait(false);
}
}
If you want to read all the users in one short and store in the memory than you use the below style. And I strongly recommend you to read user only at the time of login for the you need to add your logic in "UserStoreService" class.
public class ApplicationDbContext : IDisposable
{
private ApplicationDbContext(IList<ApplicationUser> users)
{
Users = users;
}
public IList<ApplicationUser> Users { get; set; }
public void Dispose()
{
}
public static ApplicationDbContext Create()
{
//You can use any database and hook it here
var users = new List<ApplicationUser>
{
new ApplicationUser
{
UserName = "a#a.com",
Email = "a#a.com",
Password = "test",
Roles = new List<string> {"Admin", "Admin2"}
},
new ApplicationUser
{
UserName = "a#a2.com",
Email = "a#a2.com",
Password = "test2",
Roles = new List<string> {"Admin"}
}
};
return new ApplicationDbContext(users);
}
}
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var user = await userManager.FindAsync(context.UserName.ToLower(), context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
try
{
var oAuthIdentity = await userManager.CreateIdentityAsync(user, context.Options.AuthenticationType);
var cookiesIdentity = await userManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"client_id", context.ClientId == null ? string.Empty : context.ClientId
},
{
"userName", context.UserName
}
});
var ticket = new AuthenticationTicket(oAuthIdentity, props);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
catch (Exception ex)
{
Trace.TraceError("FUSION Error ::: " + ex.Message + ex.InnerException);
Trace.TraceError(ex.Message);
}
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (var property in context.Properties.Dictionary)
if (property.Value != null)
context.AdditionalResponseParameters.Add(property.Key, property.Value);
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
context.Validated();
return Task.FromResult<object>(null);
}
}
public class AppPasswordHasher : IPasswordHasher
{
public string HashPassword(string password)
{
return password;
}
public PasswordVerificationResult VerifyHashedPassword
(string hashedPassword, string providedPassword)
{
if (hashedPassword == HashPassword(providedPassword))
return PasswordVerificationResult.Success;
return PasswordVerificationResult.Failed;
}
}
Method like "FindByNameAsync" ; where you need to read user from db on demand/login
public class UserStoreService<TUser> : IUserStore<TUser>,
IUserPasswordStore<TUser>,
IUserRoleStore<TUser>
where TUser : ApplicationUser
{
private readonly IList<TUser> _users;
public UserStoreService(IList<TUser> users)
{
_users = users;
}
public virtual Task SetPasswordHashAsync(TUser user, string passwordHash)
{
user.Password = passwordHash;
return Task.FromResult(0);
}
public virtual Task<string> GetPasswordHashAsync(TUser user)
{
return Task.FromResult(user.Password);
}
public virtual Task<bool> HasPasswordAsync(TUser user)
{
return Task.FromResult(user.Password != null);
}
public virtual Task AddToRoleAsync(TUser user, string roleName)
{
user.AddRole(roleName);
return Task.FromResult(0);
}
public virtual Task RemoveFromRoleAsync(TUser user, string roleName)
{
user.RemoveRole(roleName);
return Task.FromResult(0);
}
public virtual Task<IList<string>> GetRolesAsync(TUser user)
{
return Task.FromResult((IList<string>) user.Roles);
}
public virtual Task<bool> IsInRoleAsync(TUser user, string roleName)
{
return Task.FromResult(user.Roles.Contains(roleName));
}
public virtual void Dispose()
{
}
public virtual Task CreateAsync(TUser user)
{
user.CreatedTime = DateTime.Now;
user.UpdatedTime = DateTime.Now;
_users.Add(user);
return Task.FromResult(true);
}
public virtual Task UpdateAsync(TUser user)
{
// todo should add an optimistic concurrency check
user.UpdatedTime = DateTime.Now;
_users.Remove(user);
_users.Add(user);
return Task.FromResult(true);
}
public virtual Task DeleteAsync(TUser user)
{
return Task.FromResult(_users.Remove(user));
}
public virtual Task<TUser> FindByIdAsync(string userId)
{
return Task.FromResult(_users.FirstOrDefault(u => u.Id == userId));
}
public virtual Task<TUser> FindByNameAsync(string userName)
{
// todo exception on duplicates? or better to enforce unique index to ensure this
return Task.FromResult(_users.FirstOrDefault(u => u.Email == userName));
}
}
[Authorize(Roles = "Admin")]
public class RolesController : ApiController
{
public IEnumerable<string> Get()
{
return new[] {"value3", "value4"};
}
}
Source Code (github)
It is bound to Entity Framework and SQL Server by default, but you can easily plug in other data stores such as SharePoint, Windows Azure Storage Table Service, NoSQL databases, etc., and you get to retain control of the database schema.
Further Reading
Introduction to ASP.NET Identity

Resources