Keycloak - Add/Remove Realm role from a user using APIcalls - keycloak

passing userRepresentation.id
to keycloakServerURL + "/auth/admin/realms/XXXX/users/"+userId+"/role-mappings/realm"
I get these roles for a certain user...
[
{
"id": "xxxxxxx-1faf-4604-832a-fa7ab7eb4344",
"name": "uma_authorization",
"description": "${role_uma_authorization}",
"composite": false,
"clientRole": false,
"containerId": "XXXX"
},
{
"id": "xxxxxxx-ad9f-444e-adf4-be11ab7a3d98",
"name": "member_paid",
"description": "Membership Paid",
"composite": false,
"clientRole": false,
"containerId": "XXXX"
},
{
"id": "xxxxx-2d73-48a8-844d-a953cb570270",
"name": "offline_access",
"description": "${role_offline-access}",
"composite": false,
"clientRole": false,
"containerId": "XXXX"
}
]
I cannot figure out which API I am supposed to use to add/remove a role from/to the User.
Please can you advise what is the API I need to use
The best I can find is this one below but I don't know what the params (Path and request property should be)...
public void removeRole(JsonObject userToken, String clientId, String role) throws IOException {
/auth/admin/realms/XXXX/groups/" + role + "/role-mappings/clients/" + clientId);
...
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("id", clientId);
con.setRequestProperty("name", role);
....

Endpoints are
Get Role Mappings:
GET /auth/admin/realms/{Realm}/users/{userid}/role-mappings/realm
Add Role Mappings:
POST /auth/admin/realms/{Realm}/users/{userid}/role-mappings/realm
Delete Role Mappings:
DELETE /auth/admin/realms/{Realm}/users/{userid}/role-mappings/realm
Example Add Role
You have a role e.g. named testrole with the id dc5572a5-b7e0-4c4b-b841-dc88108df70f (you see it in the url when you have opened the keycloak admin GUI, or you fetch it with some other RestAPI Request)
Now we have a Request of Type POST to the endpoint /auth/admin/realms/{Realm}/users/{userid}/role-mappings/realm with a body of type application/json and the following body-value
[
{
"id": "dc5572a5-b7e0-4c4b-b841-dc88108df70f",
"name" : "testrole"
}
]
After successful execution you get a response with HTTP-Code 204 => The testrole - role mapping is applied to this user
Example Curl Request
curl --request POST \
--url http://localhost/auth/admin/realms/{Realm}/users/{userid}/role-mappings/realm \
--header 'authorization: Bearer eyJh......h3RLw' \
--header 'content-type: application/json' \
--data '[
{
"id": "dc5572a5-b7e0-4c4b-b841-dc88108df70f",
"name" : "testrole"
}
]'
If you want to delete it again, just send the same request (same body) but with the HTTP-method DELETE instead of POST
Please let me now if this solved your issue

Based on the post above by Evil to help me...
Using Java (and JEE 8 for the good JSON capabilities)
Get the token (using a client you set up in keycloak with access type of confidential and access to the right roles (for 9.0.0 this is even more hidden now).
public JsonObject getToken() throws IOException {
String keycloakServerURL = environmentService.getEnvironmentVariable(EnvironmentService.KEYCLOAK_SERVER);
String appClientId = environmentService.getEnvironmentVariable(EnvironmentService.APP_CLIENT_ID);
String appClientSecret = environmentService.getEnvironmentVariable(EnvironmentService.APP_CLIENT_SECRET);
URL url = new URL(keycloakServerURL + "/auth/realms/XXXXXX/protocol/openid-connect/token");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
String userpass = appClientId + ":" + appClientSecret;
String basicAuth = "Basic " + new String(Base64.getEncoder().encode(userpass.getBytes()));
con.setRequestProperty("Authorization", basicAuth);
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
/* Payload support */
con.setDoOutput(true);
DataOutputStream out = new DataOutputStream(con.getOutputStream());
out.writeBytes("grant_type=client_credentials");
out.flush();
out.close();
int status = con.getResponseCode();
BufferedReader in = new BufferedReader(new
InputStreamReader(con.getInputStream()));
JsonReader jsonReader = Json.createReader(in);
JsonObject responesAsJson = jsonReader.readObject();
in.close();
con.disconnect();
// Pretty Print of String
ObjectMapper objectMapper = new ObjectMapper();
String jSonstring = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(responesAsJson);
logger.info("Response: " + jSonstring);
// Pretty Print of String
logger.info("Response status: " + status);
//String contentString = responesAsJson.toString();
//logger.info("Response: " + contentString);
return responesAsJson;
}
then add a role (similar for remove - see post above)
public void addRole(JsonObject userToken, String userId, RoleRepresentation role) throws IOException {
String keycloakServerURL = environmentService.getEnvironmentVariable(EnvironmentService.KEYCLOAK_SERVER);
URL url = new URL(keycloakServerURL + "/auth/admin/realms/XXXXXX/users/" + userId + "/role-mappings/realm");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
String accessTokenFromUserToken = userToken.getString("access_token");
con.setRequestProperty("Authorization", "Bearer " + accessTokenFromUserToken);
con.setRequestProperty("Content-Type", "application/json");
/* Payload support */
con.setDoOutput(true);
DataOutputStream out = new DataOutputStream(con.getOutputStream());
JsonObject theBodyPart = Json.createObjectBuilder().
add("id", role.getId()).
add("name", role.getName()).
build();
JsonArray theBodyPartAsArray = Json.createArrayBuilder().add(theBodyPart).build();
String theBodyPartAsJson = theBodyPartAsArray.toString();
out.writeBytes(theBodyPartAsJson);
out.flush();
out.close();
int status = con.getResponseCode();
logger.info("Response status: " + status);
con.disconnect();
}

Related

Unsupported Grant Type with CustomGrantValidator with IdentityServer 3

I'm trying to set up our IdentityServer solution to accept a custom Grant Validator. Our API project is accessed by to UIs, one that uses Password authentication (which is working) and now one that will use a 3rd party authentication.
In our API I've set up IdentityServer like so:
Startup.cs
public void Configuration(IAppBuilder app)
{
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get());
var userService = new IdentityUserService();
factory.UserService = new Registration<IUserService>(resolver => userService);
factory.CustomGrantValidators.Add(
new Registration<ICustomGrantValidator, MyGrantValidator>());
var options = new IdentityServerOptions
{
SiteName = "My App Name",
SigningCertificate = Certificate.Get(),
Factory = factory
};
app.Map("/identity", identityServerApp =>
{
identityServerApp.UseIdentityServer(options);
});
}
MyGrantValidator.cs:
public class MyGrantValidator : ICustomGrantValidator
{
public async Task<CustomGrantValidationResult> ValidateAsync(ValidatedTokenRequest request)
{
// For now I just want a basic response. More logic will come later.
var authResult = new AuthenticateResult(
subject: "1234", // user.AccountId.ToString(),
name: "bob" //context.UserName
);
var grantResult = new CustomGrantValidationResult
{
IsError = authResult.IsError,
Error = authResult.ErrorMessage,
ErrorDescription = authResult.ErrorMessage,
Principal = authResult.User
};
return await Task.FromResult(grantResult);
}
public string GrantType => "myGrantType";
}
In my UI, I setup a client like this:
var owinContext = HttpContext.GetOwinContext();
var token = owinContext.Authentication.User.FindFirst(c => c.Type == "myToken")?.Value;
var tokenId = owinContext.Authentication.User.FindFirst(c => c.Type == ClaimTypes.Sid)?.Value;
var client = new TokenClient(
ConfigurationManager.AppSettings["IdentityServerBaseUrl"] + "/connect/token",
"MyUser",
ConfigurationManager.AppSettings["MyClientSecret"],
AuthenticationStyle.Custom
);
var tokenResponse = client.RequestCustomGrantAsync(
"myGrantType",
"read write",
new Dictionary<string, string>
{
{ "token", token },
{ "tokenId", tokenId }
}
).Result;
return Redirect(returnUrl);
When the Request is triggered, I get: unsupported_grant_type
What am I missing?
You're using a client called "MyUser" (weird name for a client, but ok). Is that client registered as one of the in-memory clients with grant type set to "custom"?

Angular 2 auth http headers empty

I currently have a very simple rest api, wich allows user authentication through basic security. My angular client receives email and password from a login input, and gets the user from the server. email and password are sent in the http headers. The problem is, when my securityfilter (JAX-rs java, jersey backend impl) i get that my auth headers are empty.
Am i sending them empty from angular??
getSecuredUser(email: string, password: string){
let headers: Headers = new Headers();
headers.append("Authorization", "Basic " + email + ":" + password);
headers.append("'Access-Control-Allow-Origin': '*' ", "application/x-www-form-urlencoded");
return this.http.get(DEFAULT_PATH + 'users/' + email, headers).subscribe(
(data: Response) => {
var result = data.json();
return result;
}
);
}
your problem is the headers for the basic auth.
getSecuredUser(email: string, password: string){
let headers: Headers = new Headers();
headers.append("Authorization", "Basic " + btoa(`${email}:${password}`);
headers.append("'Access-Control-Allow-Origin': '*' ", "application/x-www-form-urlencoded");
return this.http.get(DEFAULT_PATH + 'users/' + email, {headers: headers}).subscribe(
(data: Response) => {
var result = data.json();
return result;
}
);
}

AccessToken is null for identity server client

I have following openid options:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000",
ClientId = "mvcClient",
ClientSecret = "secret",
RedirectUri = "http://localhost:5002/signin-oidc",
PostLogoutRedirectUri = "http://localhost:5002",
ResponseType = "code id_token",
Scope = "openid profile",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var claims_to_exclude = new[]
{
"aud", "iss", "nbf", "exp", "nonce", "iat", "at_hash"
};
var claims_to_keep =
n.AuthenticationTicket.Identity.Claims
.Where(x => false == claims_to_exclude.Contains(x.Type)).ToList();
claims_to_keep.Add(new Claim("id_token", n.ProtocolMessage.IdToken));
if (n.ProtocolMessage.AccessToken != null)
{
claims_to_keep.Add(new Claim("access_token", n.ProtocolMessage.AccessToken));
}
}
}
}
I see n.ProtocolMessage.AccessToken is always null.
I configured client in identity server like this:
new Client()
{
ClientId = "mvcClient",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets = new List<Secret>()
{
new Secret("secret".Sha256())
},
// RequireConsent = false,
// where to redirect to after login
RedirectUris = { "http://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:5002" },
AllowedScopes =
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.OfflineAccess.Name,
StandardScopes.Roles.Name,
"API"
}
},
I want to know why n.ProtocolMessage.AccessToken is null and how can i get its value
UPDATE
If I change Client Type to Hybrid like this:
AllowedGrantTypes = GrantTypes.Hybrid,
and ResponseType = "code id_token token:
I get invalid_request error on server
If I try to get access token like this (in notifications):
var client = new TokenClient("http://localhost:5000/connect/token", "mvcClient", "secret");
var response = client.RequestClientCredentialsAsync("testscope").Result;
var accesstoken = response.AccessToken;
claims_to_keep.Add(new Claim("access_token", accesstoken));
The result token has only one scope(i.e testscope) instead of all other scopes defined for that client.
It's null because you're not asking for an access token.
ResponseType = "code id_token" means give the client a "Authoriziation Code" and a "Id token" on the callback. To receive an access token,either
include token in ResponseType as ResponseType = "code id_token token" & update the client flow to Hybrid flow (code + token), since that's what we're now doing.
or
fetch an access token using the /token endpoint using the "Authorization Code" available on the ProtocolMessage.
The access token should not be brought back along with code and id_token.
The right way to get it is through the back channel using client id and client secret.
Add this to the Notifications block:
AuthorizationCodeReceived = async n =>
{
var tokenClient = new TokenClient(n.Options.Authority + "/connect/token", "Client_Id", "secret");
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
else
{
string accessToken = tokenResponse.AccessToken;
//Other logic
}
}

Post reply on SharePoint online discussion board using REST API

I am trying to post reply on a particular discussion of SharePoint online discussion board through REST API but unable to do it. I don't want to use SP.utilities as this REST API will be called from Android App.
Below is the code which I am implementing:
$.ajax({
url:"../_api/web/Lists/getbytitle(listname)/items?$filter=ParentItemID eq 40",
type: "POST",
contentType: "application/json;odata=verbose",
data: JSON.stringify(itemProperties),
headers: {
"Accept": "application/json;odata=verbose",
"X-RequestDigest": $("#__REQUESTDIGEST").val(),
"IF-MATCH": "*"
},
success: function (data) {
alert("Successfully posted!!");
},
error: function (error) {
alert("error");
console.log(JSON.stringify(error));
}
});
Instead of creating reply inside discussion, it is creating a new discussion item.
Any help will be highly appreciated.
For creating a message item (reply) in Discussion Board the following properties needs to be specified:
FileSystemObjectType for a message items needs to be set to 0
ContentTypeId- content type Id of message item
ParentItemID - discussion item (container for messages) id
Regarding ParentItemID property
ParentItemID property could not be specified via message payload since it is a read only property, it means the following query for creating a message item fails:
Url /_api/web/lists/getbytitle('Discussions')/items
Method POST
Data {
'__metadata': { "type": "SP.Data.DiscussionsListItem" },
'Body': "Message text goes here",
'FileSystemObjectType': 0,
'ContentTypeId': '<MessageContentTypeId>',
'ParentItemID': <DiscussionItemId>
}
Solution
The following example demonstrates how to to create a message (reply) in Discussion Board via SharePoint REST API.
For creating a message under a discussion item (folder) the following
approach is used: once message item is created, it's getting moved
under a discussion item
var listTitle = "Discussions"; //Discussions Board title
var webUrl = _spPageContextInfo.webAbsoluteUrl;
var messagePayload = {
'__metadata': { "type": "SP.Data.DiscussionsListItem" }, //set DiscussionBoard entity type name
'Body': "Message text goes here", //message Body
'FileSystemObjectType': 0, //set to 0 to make sure Message Item is created
'ContentTypeId': '0x0107008822E9328717EB48B3B665EE2266388E', //set Message content type
'ParentItemID': 123 //set Discussion item (topic) Id
};
createNewDiscussionReply(webUrl,listTitle,messagePayload)
.done(function(item)
{
console.log('Message(reply) has been sent');
})
.fail(function(error){
console.log(JSON.stringify(error));
});
where
function executeJson(options)
{
var headers = options.headers || {};
var method = options.method || "GET";
headers["Accept"] = "application/json;odata=verbose";
if(options.method == "POST") {
headers["X-RequestDigest"] = $("#__REQUESTDIGEST").val();
}
var ajaxOptions =
{
url: options.url,
type: method,
contentType: "application/json;odata=verbose",
headers: headers
};
if("data" in options) {
ajaxOptions.data = JSON.stringify(options.data);
}
return $.ajax(ajaxOptions);
}
function createListItem(webUrl,listTitle,payload){
var url = webUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/items";
return executeJson({
"url" :url,
"method": 'POST',
"data": payload
});
}
function moveListItem(webUrl,listTitle,itemId,folderUrl){
var url = webUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/getItemById(" + itemId + ")?$select=FileDirRef,FileRef";
return executeJson({
"url" :url
})
.then(function(result){
var fileUrl = result.d.FileRef;
var fileDirRef = result.d.FileDirRef;
var moveFileUrl = fileUrl.replace(fileDirRef,folderUrl);
var url = webUrl + "/_api/web/getfilebyserverrelativeurl('" + fileUrl + "')/moveto(newurl='" + moveFileUrl + "',flags=1)";
return executeJson({
"url" :url,
"method": 'POST'
});
});
}
function getParentTopic(webUrl,listTitle,itemId){
var url = webUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/getItemById(" + itemId + ")/Folder";
return executeJson({
"url" :url,
});
}
function createNewDiscussionReply(webUrl,listTitle, messagePayload){
var topicUrl = null;
return getParentTopic(webUrl,listTitle,messagePayload.ParentItemID)
.then(function(result){
topicUrl = result.d.ServerRelativeUrl;
return createListItem(webUrl,listTitle,messagePayload);
})
.then(function(result){
var itemId = result.d.Id;
return moveListItem(webUrl,listTitle,itemId,topicUrl);
});
}

Grails RestBuilder simple POST example

I'm trying to do an OAuth2 user-credentials post to an OAuth2 service using the Grails RestBuilder plugin.
If I try to specify the post body as a map, I get an error about no message converters for LinkedHashMap.
If I try to specify the body as a String, the post goes through, but none of the variables are posted to the server action.
Here's the post:
RestBuilder rest = new RestBuilder()
def resp = rest.post("http://${hostname}/oauth/token") {
auth(clientId, clientSecret)
accept("application/json")
contentType("application/x-www-form-urlencoded")
// This results in a message converter error because it doesn't know how
// to convert a LinkedHashmap
// ["grant_type": "password", "username": username, "password": password]
// This sends the request, but username and password are null on the host
body = ("grant_type=password&username=${username}&password=${password}" as String)
}
def json = resp.json
I've also tried setting the urlVariables in the post() method call, but the username/password are still null.
This is a very simple post, but I can't seem to get it to work. Any advice would be greatly appreciated.
I solved the problem by using a MultiValue map for the body.
RestBuilder rest = new RestBuilder()
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>()
form.add("grant_type", "password")
form.add("username", username)
form.add("password", password)
def resp = rest.post("http://${hostname}/oauth/token") {
auth(clientId, clientSecret)
accept("application/json")
contentType("application/x-www-form-urlencoded")
body(form)
}
def json = resp.json
Following code works for Box connection. Spend few of hours figuring this out
String pclient_id = grailsApplication.config.ellucian.box.CLIENT_ID.toString()
String pclient_secret=grailsApplication.config.ellucian.box.CLIENT_SECRET.toString()
String pcode = params.code
log.debug("Retrieving the Box Token using following keys Client ID: ==>"+pclient_id+"<== Secret: ==>"+pclient_secret+"<== Code: ==>"+pcode)
RestBuilder rest = new RestBuilder()
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>()
form.add("client_id", pclient_id)
form.add("client_secret", pclient_secret)
form.add("grant_type", "authorization_code")
form.add("code", pcode)
def resp = rest.post("https://app.box.com/api/oauth2/token") {
accept("application/json")
contentType("application/x-www-form-urlencoded")
body(form)
}
def js = resp.json.toString()
println("sss"+js)
def slurper = new JsonSlurper()
def result = slurper.parseText(js)
println("Message:"+result.error)
render js
I found out some very easy to perform such type of action
//Get
public static RestResponse getService(String url) {
RestResponse rResponse = new RestBuilder(proxy:["localhost":8080]).get(Constants.URL+"methodName")
return rResponse
}
//POST : Send complete request as a JSONObject
public static RestResponse postService(String url,def jsonObj) {
RestResponse rResponse = new RestBuilder(proxy:["localhost":8080]).post(url) {
contentType "application/json"
json { jsonRequest = jsonObj }
}
return rResponse
}
Method 1 :
def resp = RestUtils.getService(Constants.URL+"methodName")?.json
render resp as JSON
Method 2 :
JSONObject jsonObject = new JSONObject()
jsonObject.put("params1", params.paramOne)
jsonObject.put("params2", params.paramTwo)
def resp = RestUtils.postService(Constants.URL+"methodName", jsonObject)?.json
render resp as JSON