Keycloak java client 403 when retrieving role detail - keycloak

I'm working with keycloak 8.0.1 and it's java client keycloak-admin-client library.
this is my Keycloak config
public Keycloak keycloakClient(AdapterConfig config) {
return KeycloakBuilder.builder()
.clientId(config.getResource())
.clientSecret((String) config.getCredentials().get(CredentialRepresentation.SECRET))
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.realm(config.getRealm())
.serverUrl(config.getAuthServerUrl())
.build();
}
And with this code I'd like to create user and assign him a role
final UserRepresentation user = createUserRepresentation(data);
final UsersResource userResource = getRealmResource().users();
try (Response response = userResource.create(user)) {
if (response.getStatusInfo().getFamily().equals(Response.Status.Family.SUCCESSFUL)) {
final String userId = response.getLocation().getPath().replaceAll(".*/([^/]+)$", "$1");
final RolesResource rolesResource = getRealmResource().roles();
final RoleResource roleResource = rolesResource.get(data.getRole().getRemoteName());
final RoleRepresentation role = roleResource.toRepresentation();
userResource.get(userId).roles().realmLevel().add(Collections.singletonList(role));
return userId;
} else {
throw new IllegalStateException("Unable to create user " + response.getStatusInfo().getReasonPhrase());
}
}
however it fails on line final RoleRepresentation role = roleResource.toRepresentation(); with message javax.ws.rs.ForbiddenException: HTTP 403 Forbidden.
I don't understand why am I getting this error, because my client has assigned all roles from realm-management client
create-client
impersonation
manage-authorization
manage-clients
manage-events
manage-identity-providers
manage-realm
manage-users
query-clients
query-groups
query-realms
query-users
realm-admin
view-authorization
view-clients
view-events
view-identity-providers
view-realm
view-users
Is there some config which am I missing or is it a bug?
Thanks

I just have the same problem here, while I'm trying to assign roles to an existing user using a service client (using client credentials).
The solution:
Go to Clients > Select "your" client > Go to "Service Account Roles" Tab > Select Client Roles : "realm-management" and add "view-realm" into the assigned roles.
That's it :)

Related

Keycloak stock LDAP Federation and Dynamic Roles Loaded from External Database via JTA Entities

I'm using Keycloak version 20.0.2 with stock LDAP Federation Provider, unsynced. I need for roles to be loaded from an external database but not necessarily synced with Keycloak, roles can be synced with Keycloak but preferably I would like for the roles to be looked up from external database at login and when I view the user from admin console but roles not defined in keycloak.
I don't want roles synced when LDAP users are loaded from LDAP, I need for the roles to be dynamically looked up from the database.
I have tried to use AbstractLDAPStorageMapper with JTA datasource and I am able to retrieve a list of roles in a List format when I view my user in admin console but the roles aren't defined.
#Override
public Stream<RoleModel> getRoleMappingsStream() {
System.out.println("]--> getRoleMappingsStream");
Stream<RoleModel> roleMappings = super.getRoleMappingsStream();
String email = delegate.getEmail();
List<String> rolesDB = getRolesFromDB(email);
for (String roleDB : rolesDB){
System.out.println("]--> " + roleDB);
RoleModel roleModel = realm.getRole(roleDB);
if (roleModel == null){
roleModel = realm.addRole(roleDB);
logger.debugf("Adding role [%s] ", roleDB);
System.out.println("[--> Adding role " + roleDB);
}
logger.debugf(
"Granting role [%s] to user [%s] during user import from LDAP",
roleDB,
email
);
System.out.println("Granting role " + roleDB + " to user " + email + " during user import from LDAP");
delegate.grantRole(roleModel);
roleMappings = Stream.concat(roleMappings, Stream.of(roleModel));
}
RoleModel role = getRole(realm);
if (role != null) {
roleMappings = Stream.concat(roleMappings, Stream.of(role));
}
return roleMappings;
}
It turned out that my code worked, my function getRolesFromDB wasn't returning any values. After fixing the function the code started working.

How to secure Superset '/login/' endpoint

Recently I integrated superset with my web application so that when an user who is authenticated by my web application can enter superset and view/edit/create dashboards based on their role just by clicking the link no need to even login. For doing this I had to bypass the login for which I referred this article.
Custom SecurityManager I used to bypass login
class CustomAuthDBView(AuthDBView):
#expose('/login/', methods=['GET', 'POST'])
def login(self):
redirect_url = self.appbuilder.get_url_for_index
user_name = request.args.get('username')
user_role = request.args.get('role')
if user_name is not None:
user = self.appbuilder.sm.find_user(username=user_name)
if not user:
role = self.appbuilder.sm.find_role(user_role)
user = self.appbuilder.sm.add_user(user_name, user_name, 'last_name', user_name + "#domain.com", role, password = "password")
if user:
login_user(user, remember=False)
return redirect(redirect_url)
else:
print('Unable to auto login', 'warning')
return super(CustomAuthDBView,self).login()
class CustomSecurityManager(SupersetSecurityManager):
authdbview = CustomAuthDBView
def __init__(self, appbuilder):
super(CustomSecurityManager, self).__init__(appbuilder)
So according to above code using url http://localhost:8088/login?username=John will login the user John internally or if user John does not exist account is created with some role which is based on the role of user in my web application
Now the problem is anyone who can guess this url http://localhost:8088/login?username=USER_NAME can create their account in superset, so how to protect or secure this '/login' endpoint
You can use the API so that you dont expose request details over the URL.
from flask_appbuilder.api import BaseApi, expose
from . import appbuilder
class LoginApi(BaseApi):
resource_name = "login"
#expose('/loginapi/', methods=['GET','POST'])
##has_access
def loginapi(self):
if request.method == 'POST':
username = request.json['username']
password = request.json['password']
appbuilder.add_api(LoginApi)

Authorization has been denied for this request while integrating Identity server4 (ver 3.0.0) token into Microsoft.AspNet.WebApi (Framework 4.6.2)"

I am using Identity server4 (ver 3.0.0) and want to authenticate Microsoft.AspNet.WebApi " version="5.2.7" targetFramework="net462"
I have installed identity server3 in AspNet.WebApi project.While I am authenticating .net web api request using Bearer token
that i got from identity server4.this showing me unAuthorized Access. I am attaching some screenshots here :
Postman Screenshot:
https://prnt.sc/qu0sb6
Blockquote
public void Configuration(IAppBuilder app)
{
HttpConfiguration webApiConfig = new HttpConfiguration();
webApiConfig.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.Use<ExceptionHandler>();
ConfigureAuth(app);
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44353/",
ClientId = "ConsoleApp_ClientId",
ClientSecret = "secret_for_the_consoleapp",
RequiredScopes = new[] { "SmarttrackReportAPI" },
DelayLoadMetadata = true
});
app.Use<SessionAuthenticator>();
WebApiConfig.Register(webApiConfig);
HttpServer webApiServer = new HttpServer(webApiConfig);
app.UseWebApi(webApiServer);
}
Installed package Screenshot:
https://prnt.sc/qu0sv1
Please help.

Generate SPNEGO Token Failured

I tried to generate the token which can be used as the HTTP header to authenticate to the HDFS WebHDFS URL and Oozie REST API URL.
I referenced the url below to have the below code to generate the Negotiate token.
https://www.ibm.com/support/knowledgecenter/en/SS7JFU_8.5.5/com.ibm.websphere.express.doc/ae/tsec_SPNEGO_token.html
public class TokenCreation {
private static final String SPNEGO_OID = "1.3.6.1.5.5.2";
private static final String KERBEROS_OID = "1.2.840.113554.1.2.2";
public static byte[] genToken(String principal) {
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
byte[] spnegoToken = new byte[0];
try {
Oid spnegoMechOid = new Oid(SPNEGO_OID);
Oid krb5MechOid = new Oid(KERBEROS_OID);
GSSCredential clientGssCreds = null;
GSSManager manager = GSSManager.getInstance();
GSSName gssUserName = manager.createName(principal, GSSName.NT_USER_NAME, krb5MechOid);
clientGssCreds = manager.createCredential(gssUserName.canonicalize(krb5MechOid),
GSSCredential.INDEFINITE_LIFETIME,
krb5MechOid,
GSSCredential.INITIATE_ONLY);
clientGssCreds.add(gssUserName,
GSSCredential.INDEFINITE_LIFETIME,
GSSCredential.INDEFINITE_LIFETIME,
spnegoMechOid, GSSCredential.INITIATE_ONLY);
GSSName gssServerName = manager.createName(principal, GSSName.NT_USER_NAME);
GSSContext clientContext = manager.createContext(gssServerName.canonicalize(spnegoMechOid),
spnegoMechOid,
clientGssCreds,
GSSContext.DEFAULT_LIFETIME);
// optional enable GSS credential delegation
clientContext.requestCredDeleg(true);
// create a SPNEGO token for the target server
spnegoToken = clientContext.initSecContext(spnegoToken, 0, spnegoToken.length);
} catch (GSSException e) {
e.printStackTrace();
}
return spnegoToken;
}
But after running the above code, I always got the below prompt:
2019-09-25 14:12:51 760 [INFO] [pool-2-thread-1] c.s.n.c.u.security.KrbUtils - after loginUserFromKeytab............AtoimcUser:HTTP/host1.exmaple.com#EXAMPLE.COM
2019-09-25 14:12:51 760 [INFO] [pool-2-thread-1] c.s.n.app.oozie.OozieAppCaller - ->>>>>>User Name is HTTP/host1.exmaple.com#EXAMPLE.COM
2019-09-25 14:12:51 760 [INFO] [pool-2-thread-1] c.s.n.app.oozie.OozieAppCaller - ->>>>>>Mode is KERBEROS
>>>KinitOptions cache name is /tmp/krb5cc_0
Kerberos username [root]: ^C^C^C
Kerberos password for root:
You can see at the end of the above output log.
The "Kerberos username" is always prompt to ask for username.
Also I have tried to manually run kinit the keytab.
and the above class can generate the token successfully.
But manually run kinit is NOT the way I wanted.
Would you please help it?
Thanks.
Kerberos and SPNEGO support in Java is cumbersome unfortunately.
I've created a small library to simplify some Kerberos use cases: https://github.com/bedrin/kerb4j
You can use it like this to generate SPNEGO token:
SpnegoClient spnegoClient = SpnegoClient.loginWithKeyTab("svc_consumer", "/opt/myapp/consumer.keytab");
URL url = new URL("http://api.provider.acme.com/api/operation1");
SpnegoContext context = spnegoClient.createContext("http://provider.acme.com"); // Will result in HTTP/provider.acme.com SPN
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Authorization", context.createTokenAsAuthroizationHeader());

Is it possible to secure a ColdFusion 11 REST Service with HTTP BASIC Authentication?

I am setting up a simple REST Service in ColdFusion 11. The web server is IIS 8.5 on Windows Server 2012R2.
This REST Service needs to be secured to prevent unauthorized users from accessing/writing data. For the time being, there will be only one authorized user, so I want to keep authentication/authorization as simple as possible. My initial thought is to use HTTP BASIC Authentication.
Here's the setup for the REST Service:
Source Directory: C:\web\site1\remoteapi\
REST path: inventory
To implement this, I configured the source directory of the REST Service in IIS to authorize only one user, disable Anonymous authentication, and enable Basic authentication.
When I call the source directory directly in a browser (i.e. http://site1/remoteapi/inventory.cfc?method=read), I am presented with the Basic authentication dialog.
However, when I attempt to request the REST path (http://site1/rest/inventory/), I am not challenged at all.
How can I implement HTTP BASIC authentication on the REST path?
So, due to the need to get this done without much delay, I went ahead and using some principles from Ben Nadel's website, I wrote my own authentication into the onRequestStart() method of the REST Service's Application.cfc. Here is the basic code, though it uses hard-coded values in the VARIABLES scope to validate the username and password and also does not include any actual "authorization" setting:
public boolean function onRequestStart(required string targetPage) {
LOCAL.Response = SUPER.onRequestStart(ARGUMENTS.targetpage);
if (!StructKeyExists(GetHTTPRequestData().Headers, "Authorization")) {
cfheader(
name="WWW-Authenticate",
value="Basic realm=""REST API Access"""
);
LOCAL.RESTResponse = {
status = 401,
content = {Message = "Unauthorized"}
};
restSetResponse(LOCAL.RESTResponse);
}
else {
LOCAL.IsAuthenticated = true;
LOCAL.EncodedCredentials =
GetToken( GetHTTPRequestData().Headers.Authorization, 2, " " );
// Credential string is not Base64
if ( !ArrayLen(
REMatch(
"^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$",
LOCAL.EncodedCredentials
)
)
) {
LOCAL.IsAuthenticated = false;
}
else {
// Convert Base64 to String
LOCAL.Credentials =
ToString(ToBinary( LOCAL.EncodedCredentials ));
LOCAL.Username = GetToken( LOCAL.Credentials, 1, ":" );
LOCAL.Password = GetToken( LOCAL.Credentials, 2, ":" );
if ( LOCAL.Username != VARIABLES.CREDENTIALS.Username
|| LOCAL.Password != VARIABLES.CREDENTIALS.Password
) {
LOCAL.IsAuthenticated = false;
}
}
if (!LOCAL.IsAuthenticated) {
LOCAL.Response = {
status = 403,
content = {Message = "Forbidden"}
};
restSetResponse(LOCAL.Response);
}
}
return LOCAL.Response;
}