Spring ACL adding ACE when the current user has no permission on the ACL - spring

Short Question: When a new user signs up on my website I need to add a read permission to a domain object. Spring ACLs does a check using the current user's permissions to see if a permission can be added. This will always fail because the user has just signed up and has no permissions on the object that they need a read permission on. Is there a way to skip the security check in certain situations?
Long question: The website I'm working on has an invite system that is done using tokens and emails. User A creates an organization and becomes the owner of that organization. User A can then invite User B to the organization by email, an email will be sent telling them to signup, etc. When User B gets the invite they sign up and the token is looked up. This token has a relation to an organization and at this point I try to give the user a ReadPermission but I get the error:
"org.springframework.security.acls.model.NotFoundException: Unable to locate a matching ACE for passed permissions and SIDs"
Is there a way around this security check?
Or
How far into Spring Security do I need to go to change this setup?

Found the answer to this when reading spring docs. You can use a Runnable and DelegatingSecurityContextExecutor to run the required call as a different user.
User newUser = getCurrentUser();
User notOriginalUser = accountsService.findUserById(someId);
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = new UsernamePasswordAuthenticationToken(notOriginalUser, "doesnotmatter", authoritiesList);
context.setAuthentication(authentication);
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor = new DelegatingSecurityContextExecutor(delegateExecutor, context);
class PermissionRunnable implements Runnable {
String u;
Organization o;
PermissionRunnable(Organization org, String username) {
o = org;
u = username;
}
public void run() {
permissionsService.setReadableByPrincipal(o, new PrincipalSid(u));
}
}
Runnable originalRunnable = new PermissionRunnable(org, newUser.getUsername());
executor.execute(originalRunnable);

Related

How to add/manage user claims at runtime in IdentityServer4

I am trying to use IdentityServer4 in a new project. I have seen in the PluralSight video 'Understanding ASP.NET Core Security' that IdentityServer4 can be used with claims based security to secure a web API. I have setup my IdentityServer4 as a separate project/solution.
I have also seen that you can add an IProfileService to add custom claims to the token which is returned by IdentityServer4.
One plan is to add new claims to users to grant them access to different parts of the api. However I can't figure out how to manage the claims of the users on the IdentityServer from the api project. I assume I should be making calls to IdentotyServer4 to add and remove a users claims?
Additionally is this a good approach in general, as I'm not sure allowing clients to add claims to the IdentityServer for their own internal security purposes makes sense - and could cause conflicts (eg multiple clients using the 'role' claim with value 'admin'). Perhaps I should be handling the security locally inside the api project and then just using the 'sub' claim to look them up?
Does anyone have a good approach for this?
Thanks
Old question but still relevant. As leastprivilege said in the comments
claims are about identity - not permissions
This rings true, but identity can also entail what type of user it is (Admin, User, Manager, etc) which can be used to determine permissions in your API. Perhaps setting up user roles with specific permissions? Essentially you could also split up Roles between clients as well for more control if CLIENT1-Admin should not have same permissions as CLIENT2-Admin.
So pass your Roles as a claim in your IProfileService.
public class ProfileService : IProfileService
{
private readonly Services.IUserService _userService;
public ProfileService(Services.IUserService userService)
{
_userService = userService;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
try
{
switch (context.Client.ClientId)
{
//setup profile data for each different client
case "CLIENT1":
{
//sub is your userId.
var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");
if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
{
//get the actual user object from the database
var user = await _userService.GetUserAsync(long.Parse(userId.Value));
// issue the claims for the user
if (user != null)
{
var claims = GetCLIENT1Claims(user);
//add the claims
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}
}
}
break;
case "CLIENT2":
{
//...
}
}
}
catch (Exception ex)
{
//log your exceptions
}
}
// Gets all significant user claims that should be included
private static Claim[] GetCLIENT1Claims(User user)
{
var claims = new List<Claim>
{
new Claim("user_id", user.UserId.ToString() ?? ""),
new Claim(JwtClaimTypes.Name, user.Name),
new Claim(JwtClaimTypes.Email, user.Email ?? ""),
new Claim("some_other_claim", user.Some_Other_Info ?? "")
};
//----- THIS IS WHERE ROLES ARE ADDED ------
//user roles which are just string[] = { "CLIENT1-Admin", "CLIENT1-User", .. }
foreach (string role in user.Roles)
claims.Add(new Claim(JwtClaimTypes.Role, role));
return claims.ToArray();
}
}
Then add [Authorize] attribute to you controllers for your specific permissions. This only allow specific roles to access them, hence setting up your own permissions.
[Authorize(Roles = "CLIENT1-Admin, CLIENT2-Admin, ...")]
public class ValuesController : Controller
{
//...
}
These claims above can also be passed on authentication for example if you are using a ResourceOwner setup with custom ResourceOwnerPasswordValidator. You can just pass the claims the same way in the Validation method like so.
context.Result = new GrantValidationResult(
subject: user.UserId.ToString(),
authenticationMethod: "custom",
claims: GetClaims(user));
So like leastprivilege said, you dont want to use IdentityServer for setting up permissions and passing that as claims (like who can edit what record), as they are way too specific and clutter the token, however setting up Roles that -
grant them access to different parts of the api.
This is perfectly fine with User roles.
Hope this helps.

403 error trying to access Google Admin SDK with service account

I know that this question has been asked before
Google Admin sdk directory 403
Getting a 403 - Forbidden for Google Service Account
but I've tried all of the solutions suggested and this still won't work for me.
So. My code looks like this:
public static Directory getDirectoryService(String userEmail)
throws GeneralSecurityException, IOException, TokenResponseException {
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes(Arrays.asList(DirectoryScopes.ADMIN_DIRECTORY_GROUP,
DirectoryScopes.ADMIN_DIRECTORY_USER,
DirectoryScopes.ADMIN_DIRECTORY_DOMAIN))
.setServiceAccountUser(userEmail)
.setServiceAccountPrivateKeyFromP12File(
new java.io.File(SERVICE_ACCOUNT_PKCS12_FILE_PATH))
.build();
Directory service = new Directory.Builder(httpTransport, jsonFactory, null)
.setHttpRequestInitializer(credential).build();
return service;
}
When I call this routine, passing a Super Admin email address to it, it appears to run successfully. But If I debug before I return from this call I can see that the AccessToken is null. I know the credentials are sound, because the same application is already using the Drive API successfully with this service account and P12 file.
Then, my actual calls to the API look like this:
Directory directory = getDirectoryService("my super user email");
// Print the first 10 users in the domain.
Directory.Users.List list = directory.users().list();
list.setDomain("integrity.co.uk");
list.setMaxResults(10);
list.setOrderBy("email");
Users result = list.execute();
List<User> users = result.getUsers();
But when I get to the list.execute() line the application crashes out:
Exception in thread "main" java.lang.NullPointerException
at com.google.api.client.repackaged.com.google.common.base.Preconditions.checkNotNull(Preconditions.java:191)
at com.google.api.client.util.Preconditions.checkNotNull(Preconditions.java:127)
at com.google.api.client.json.jackson2.JacksonFactory.createJsonParser(JacksonFactory.java:96)
at com.google.api.client.json.JsonObjectParser.parseAndClose(JsonObjectParser.java:85)
at com.google.api.client.json.JsonObjectParser.parseAndClose(JsonObjectParser.java:81)
at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:88)
at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:287)
at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:307)
at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:268)
at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:489)
at com.google.api.client.auth.oauth2.Credential.intercept(Credential.java:217)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:859)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:410)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:343)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:460)
at googleImageUploader.googleImageUploader.main(googleImageUploader.java:162)
Other threads suggest:
Making sure organisation-wide admin access is delegated to this service account in the Admin Console - it definitely is with the scopes:
https://www.googleapis.com/auth/admin.directory
https://www.googleapis.com/auth/admin.directory.group
https://www.googleapis.com/auth/admin.directory.user
https://www.googleapis.com/auth/drive
Making sure a service account user is specified (which it is, and this user is definitely a Super Admin).
I'm tearing my hair out - can anyone help?!
Thanks.

Google Directory API returns Not Authorized when call users().list().execute()

I need to read the list of users (and groups) from my google domain.
So I went to my Google APIs Console and enabled Admin SDK and created a Service Account to call Google APIs. I use the following google libraries
google-api-services-admin-directory_v1-rev11-1.16.0-rc.jar
google-api-client-1.16.0-rc.jar
My code is
/*
* Global instance of the JSON factory.
*/
final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
/*
Global instance of the HTTP transport.
*/
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
Collection<String> scopeList = new ArrayList<>();
scopeList.add(DirectoryScopes.ADMIN_DIRECTORY_USER);
scopeList.add(DirectoryScopes.ADMIN_DIRECTORY_GROUP);
scopeList.add(DirectoryScopes.ADMIN_DIRECTORY_GROUP_MEMBER);
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId("nnnnnn#developer.gserviceaccount.com")
.setServiceAccountScopes(scopeList)
.setServiceAccountPrivateKeyFromP12File(new File("/Path/To/KeyFile/nnnnn-privatekey.p12"))
// .setServiceAccountUser("admin#mydomain.org")
.build();
Directory admin = new Directory.Builder(httpTransport, JSON_FACTORY, credential)
.setApplicationName("Test")
.setHttpRequestInitializer(credential).build();
Users users = admin.users().list().setDomain("mydomain.org").execute();
And I receive this on the last line
Error
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Not Authorized to access this resource/api",
"reason" : "forbidden"
} ],
"message" : "Not Authorized to access this resource/api"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:145)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:113)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:40)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest$1.interceptResponse(AbstractGoogleClientRequest.java:312)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1045)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:410)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:343)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:460)
If I uncomment the commented line (.setServiceAccountUser("admin#mydomain.org")) then I get a different error
Exception in thread "main" com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "access_denied"
}
at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:105)
at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:287)
at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:307)
at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:269)
at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:489)
at com.google.api.client.auth.oauth2.Credential.intercept(Credential.java:217)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:858)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:410)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:343)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:460)
My account (admin#mydomain.org) is a super administrator. I suspect the Service Account needs to be granted access for the users API scope. But I can not find where I can grant this access. I have a classic UI mode of my Google CPanel. And I don't have "Manage client API access" page in the Advanced tools.
Also I'm not sure what I should use as an Application Name at .setApplicationName("Test").
Thanks for any help
you can go to "security" settings in the admin console (admin.google.com/AdminHome?chromeless=1&pli=1#SecuritySettings:); then click on advance settings > Manage third party OAuth Client access. After this map your client id(generated from appconsole code.google.com/apis/console under API access for oath2) and "One or More API Scopes". Use comma separated scopes as mentioned there. For google directory you can use https://www.googleapis.com/auth/admin.directory.group,https://www.googleapis.com/auth/admin.directory.user
Hope after this it works :)
I had the exact same problem, and was stucked on this sample
What helped me :
1/ I did not Delegate domain-wide authority to your service account, as suggested by Jay Lee. But after that, I still had the problem.
2/ Then, according to this post, the call to setServiceAccountUser(yourAdminAccount#yourDomain.com) is mandatory.
Google Directory API works with Compute Engine default service account, you do not need to setup Google Drive domain-wide. The only thing: you have to set serviceAccountUser, which is not supported in JSON based credentials. So you can
use P12 keys
user JSON credentials with workaround:
make credential copy:
import static com.google.api.client.googleapis.util.Utils.getDefaultJsonFactory;
import static com.google.api.client.googleapis.util.Utils.getDefaultTransport;
private static final String APPLICATION_NAME = "AnyAppName";
private final List<String> SCOPES = ImmutableList.of(
DirectoryScopes.ADMIN_DIRECTORY_GROUP_MEMBER, DirectoryScopes.ADMIN_DIRECTORY_USER, DirectoryScopes.ADMIN_DIRECTORY_GROUP);
private Directory service;
#PostConstruct
void init() throws GeneralSecurityException, IOException {
GoogleCredential credential;
try (InputStream is = new FileInputStream("./config/client_secret.json")) {
credential = GoogleCredential.fromStream(is);
}
GoogleCredential credentialWithUser = new GoogleCredential.Builder()
.setTransport(getDefaultTransport())
.setJsonFactory(getDefaultJsonFactory())
.setServiceAccountUser("admin#yourdomain.ru") // <--- mail of domain's admin
.setServiceAccountId(credential.getServiceAccountId())
.setServiceAccountScopes(SCOPES)
.setServiceAccountPrivateKey(credential.getServiceAccountPrivateKey())
.setServiceAccountPrivateKeyId(credential.getServiceAccountPrivateKeyId())
.setTokenServerEncodedUrl(credential.getTokenServerEncodedUrl()).build();
service = new Directory.Builder(getDefaultTransport(), getDefaultJsonFactory(), credentialWithUser).setApplicationName(APPLICATION_NAME).build();
}
public void members() throws IOException {
Members members = service.members().list("groupName#yourdomain.ru").execute();
System.out.println(members);
}
For my trial G Suite account it works!
You can grant the service account access to certain scopes in the Control Panel as explained in the Google Drive domain-wide documentation. Just use Admin SDK scopes instead.
The application name is used in the User-Agent header of requests and so is not overly important, just use your apps name and maybe version.

Revoke access granted to my app Google Drive API

How do I revoke access that has been granted to my Google Drive web application so that upon the user's next use he is asked for permissions afresh?
For revoking your access token, you need to "GET" (!) this url:
https://accounts.google.com/o/oauth2/revoke?token={token}
where {token} is the value of your token, as explained here:
https://developers.google.com/accounts/docs/OAuth2WebServer#tokenrevoke
For Java API (don't know for other languages), as of 9th of Sept 2012, there is no API for this.
I managed to revoke a token with this code:
class myGoogleApi {
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
...
public revoke(String token) {
HttpRequestFactory factory = HTTP_TRANSPORT.createRequestFactory();
GoogleUrl url = new GoogleUrl("https://accounts.google.com/o/oauth2/revoke?token="+token);
HttpRequest request = factory.buildGetRequest(url);
HttpResponse response = request.execute();
...
}
If you clobbered all the refresh tokens in your DB, adding the query parameter approval_prompt=force to the auth request will fix that. It'll result in the refresh tokens getting reissued when the user next approves the request.
In order to revoke the access go to the below url
https://security.google.com/settings/security/permissions?pli=1
Choose your apps that you need to revoke and click on remove.
Visit https://accounts.google.com/b/0/IssuedAuthSubTokens?hl=en for the list of applications and sites that you granted access to. Next to each of them you'll find a Revoke Access button.
The instructions to get to that page are at http://support.google.com/accounts/bin/answer.py?hl=en&answer=41236
Using Google Play Services:
http://developer.android.com/reference/com/google/android/gms/auth/GoogleAuthUtil.html
Add https://www.googleapis.com/auth/userinfo.profile to your scope.
Example:
String scope="oauth2:https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"
final String token = GoogleAuthUtil.getToken(context, "xxxx#gmail.com", scope);
OR "brute force"
Intent res = new Intent();
res.addCategory("account:xxxx#gmail.com");
res.addCategory("scope:oauth2:https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile");
res.putExtra("service", "oauth2:https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile");
Bundle extra= new Bundle();
extra.putString("androidPackageName","com.your.package");
res.putExtra("callerExtras",extra);
res.putExtra("androidPackageName","com.your.package");
res.putExtra("authAccount","xxxx#gmail.com");
String mPackage = "com.google.android.gms";
String mClass = "com.google.android.gms.auth.TokenActivity";
res.setComponent(new ComponentName(mPackage,mClass));
startActivityForResult(res,100);
Now, when you revoke the access here https://accounts.google.com/IssuedAuthSubTokens the application shows you the window for permission again in the device.

MVC 3 Not Recognizing Windows Role (Group)?

Has anybody come across occurrences where MVC does not recognize roles from Windows? I thought that roles translated to groups in Windows, but for some reason when I add a user to a group, check in MVC (using windows authentication) if that user.IsInRole("GroupJustAddedTo") always returns false. I have no idea why....Working in Server 2003 R2 with Windows Authentication enabled around the board. Confused :~( ???
Without knowing anything else, it makes me wonder if perhaps your MVC is not really connected to your AD server. Also, perhaps the user that is fetching the groups doesn't have sufficient privileges? Just some initial thoughts.
EDIT
We ended up writing our own RoleProvider. Here is the overloaded GetRolesForUser code
public override string[] GetRolesForUser(string userName)
{
List<string> allRoles = new List<string>();
PrincipalContext context;
context = new PrincipalContext(ContextType.Domain, "hlpusd.k12.ca.us", "DC=hlpusd,DC=k12,DC=ca,DC=us");
UserPrincipal user = UserPrincipal.FindByIdentity(context, userName);
PrincipalSearchResult<Principal> usergroups = user.GetGroups(); // list of AD groups the user is member of
IEnumerator<Principal> eGroup = usergroups.GetEnumerator();
while (eGroup.MoveNext())
{
allRoles.Add(eGroup.Current.Name);
}
return allRoles.ToArray();
}

Resources