How to limit some endpoints w.r.t logged in user in swagger `drf-yasg`? - django-rest-framework

I have setup drf-yasg, but unable to limit some endpoints for specific users only. Tried using public=False, but how to tell it that which users should see which of the endpoints ?
schema_view = get_schema_view(openapi.Info(
title="API Documentation",
default_version='v1',
), public=False, permission_classes=(permissions.AllowAny,), ).with_ui("swagger", cache_timeout=0)
return schema_view

Related

Keycloak HTTP 401 Unauthorized while creating new user using spring boot service

Getting javax.ws.rs.NotAuthorizedException: HTTP 401 Unauthorized while trying to create new user in keycloak with spring boot service.
In KeycloakConfig class. it is breaking during creating keycloak instance with HTTP 401 Unauthorized. "message": "javax.ws.rs.NotAuthorizedException: HTTP 401 Unauthorized",
Am I missing anything? I dont have any extra role configuration in the keycloak client or realm role.
I was thinking to add ".authorization(...) in KeycloakBuilder.builder but I'm not sure what to specify.
public static Keycloak getInstance(User user) {
if (keycloak == null) {
keycloak = KeycloakBuilder.builder()
.serverUrl("http://localhost:8080/auth")
.realm("myrealm")
.username(user.getUsername()).password(user.getPassword())
.grantType(OAuth2Constants.PASSWORD)
.clientId("myclient")
.clientSecret("0a53569564d2a748a0a5482699")
.resteasyClient(new ResteasyClientBuilder().connectionPoolSize(10).build()).build();
}
return keycloak;
}
In service class
UsersResource usersResource = KeycloakConfig.getInstance(user).realm("myrealm").users();
CredentialRepresentation credentialRepresentation = createPasswordCredentials(user.getPassword());
UserRepresentation kcUser = new UserRepresentation();
kcUser.setUsername(user.getEmail());
kcUser.setCredentials(Collections.singletonList(credentialRepresentation));
kcUser.setFirstName(user.getFirstName());
kcUser.setLastName(user.getLastName());
kcUser.setEmail(user.getEmail());
kcUser.setEnabled(true);
kcUser.setEmailVerified(false);
javax.ws.rs.core.Response response = usersResource.create(kcUser);
System.out.println("kcUser: " + kcUser.toString());
System.out.printf("Repsonse: %s %s%n", response.getStatus(), response.getStatusInfo());
in properties file
keycloak.realm=myrealm
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.resource=myclient
keycloak.credentials.secret=0a5356444d26a748a0a5482699
keycloak.ssl-required= external
keycloak.use-resource-role-mappings= true
keycloak.bearer-only=true
keycloak.principal-attribute=preferred_username
Due to missing imports I assume that KeycloakBuilder is comming from package org.keycloak.admin.client.KeycloakBuilder
Based on your provided code examples you are trying to instantiate a Keycloak instance with an user you just want to create. That user is lack of the privileges to create a new user.
That said you need to create an Keycloak instance with an user that has admin rights / roles. At least "manage-users, view-clients, view-realm, view-users"
keycloak = KeycloakBuilder.builder()
.serverUrl(KEYCLOAK_SERVER_URL)
.realm("master")
.clientId("admin-cli")
.username("admin")
.password("admin")
.build();
With admin access you are able to create users for several realms.
// Create a new user
final UserRepresentation newUser = new UserRepresentation();
newUser.setUsername("username");
newUser.setFirstName("fristname");
newUser.setLastName("lastname");
newUser.setEmail("username#example.com");
newUser.setEnabled(true);
// Now you need to get the realm you want the user to add to and the user ressource
final UserResource realm = keycloak.realm("nameOfTheRealm").users();
// Create the user for the realm
final Response createUserResponse = userRessource.create(newUser);
// check response if everything worked as expected
Make sure the admin account is created under 'myrealm'. You cannot use the default admin account (master realm) to create the user for 'myrealm'
In order for a user to have the right to manipulate and call Keycloak's API, you must set the client role for that user
Step 1: User should create a administrators like "admin" password "abc123" in your client "myrealm"
Step 2: Click admin user after move to Roles mapping tab and assign some necessary permissions to admin user as below
Step 3: Replace password in .username(user.getUsername()).password(user.getPassword()) with "abc123"
Hope to help you!

Retrieving values from reactive mongodb based on Mono

I'm building an application based on a Spring gateway & webflux which is integrating with okta as authorization server.
After the frontend is authenticating the user with the okta server, it's getting a token which is must be used per request header as Bearer token.
The end point which I'm developing now is responsible to retrieve all menus related to that authenticated user from reactive mongodb based on the assigned group(s) to that user.
The user's group(s) is/are extracted from the okta JWT token, after that i should inquire to select all menus related to these group(s).
The problem is that I'm not able to use the extracted group(s) name(s) to use it for inquiring from the reactive mongodb
#RequestMapping("/menus")
public Mono oauthUserInfo() {
return ReactiveSecurityContextHolder.getContext().filter(c -> Objects.nonNull(c.getAuthentication()))
.map(s -> s.getAuthentication().getAuthorities().stream().filter((g) ->g.getAuthority().startsWith("GR_")));
}
Accessing the above endpint is retrieving the authenticated user group(s) as below:
[
{
"authority": "GR_user"
},
{
"authority": "GR_admin"
}
]
My main question is that, How can i use it to retrieve the related menus from my reactive mongodb using 'in' operator and retrieve Flux< Menu >
After several trails, finally I achieved my target by extracting the groups names from the Bearer token as below
#RequestMapping("/menu")
public Flux<Menu> newEndPoint(){
return ReactiveSecurityContextHolder.getContext().filter(c -> Objects.nonNull(c.getAuthentication())).cast(SecurityContextImpl.class)
.map( a -> a.getAuthentication().getPrincipal()).cast(Jwt.class)
.map( a -> a.getClaims().get("groups").toString().substring(1,a.getClaims().get("groups").toString().length()-1).replaceAll("\"",""))
.map( a -> a.split(","))
.flatMapIterable( a -> Arrays.asList(a))
.flatMap( a -> customMenuRepository.findByGroup(a))
;
}
The operations that done on 2nd & 3rd map calls are due to getting the group values as following ["GR_user", "GR_admin"], and I need it like a list of strings without [ or " or ,

Token Passport Users

I'm new to building APIs, and I'm using Laravel and some packages, like Passport.
In this project I'm creating an API to communicate with a mobile app and make some tasks, like creating users and adding some information related to the users.
I'm already getting the idea of how it works with tokens, and I already have almost everything done. My only question is, for example, before I create a register user to receive the token I have other information being presented in the mobile app, like news, and some listing information that is not needed to login or register.
In my API I already have these routes ready, but I cant access this information because I need a token.
How do iI handle this situation? When I need to access information, where I cant present it?
Use your controllers to retrieve the data you need from your database and then return a response with the data and create a token for the user:
...
$status = 200;
$response = [
'data' => Your data (user info, listings, news...),
'token' => Auth::user()->createToken("YourTokenName")->accessToken,
];
return response()->json($response, $status);
Then you could store that token in localStorage:
axios.post('your-api-route')
.then((response) => {
localStorage.setItem(`${"YourTokenName"}.jwt`, response.token);
...
})
And finally attach that token to the api routes that require authentication:
axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem(`${YourTokenName}.jwt`);

Running IdentityServer4 and API and a Web App from a single 'site'

We have a legacy system written in Core MVC 1, using IdentityServer4 for API access and Identity for user management. The site includes a set of API controllers as well, which a mobile application connects to for authentication and data.
We have been having general stability issues, which we have not been able to get to the bottom of. We have decided to upgrade the system to the latest version of MVC Core and in the process IdentityServer4 requires an upgrade.
The problem is that the authentication pipeline has changed dramatically between versions (Core MVC 1 - 2 and Identity 1 - 2) and we are unable to determine a configuration that works.
In short we need:
Cookie Authentication for web site access
OAuth 2 password grant flow for app access
However, despite this setup working on the legacy version, it does not seem to want to play ball on the newer setup. It seems we can have one or the other, but not both. There doesn't appear to be any example projects available anywhere that demonstrate such a setup.
I understand this setup is not ideal in that these systems should be split out, and I am going to be making a recommendation as such. I have seen hints of routing api requests through a pipeline setup for Bearer authentication using MapFrom but haven't managed to determine a working setup.
UPDATE: Startup.cs
services.AddIdentity<ApplicationUser, IdentityRole>(o =>
{
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
var AuthServerConfig = new IdentityServerConfig(Configuration.GetSection("IdentityServer"));
var IdentityCert = AuthServerConfig.GetCerttificate();
var IdentityConfig = services.AddIdentityServer()
.AddInMemoryIdentityResources(AuthServerConfig.GetIdentityResources())
.AddInMemoryApiResources(AuthServerConfig.GetApiResources())
.AddInMemoryClients(AuthServerConfig.GetClients())
.AddAspNetIdentity<ApplicationUser>();
//to secure the API
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = AuthServerConfig.Settings.RootUrl;
options.RequireHttpsMetadata = false;
options.ApiName = AuthServerConfig.Settings.Scope;
});
And in the Configure Method we have:
app.UseIdentityServer();
app.UseAuthentication();
The stage we are at now is that IdentityServer seems to be operational in that a token can be requested. You can call into an API endpoint and gain access so long as that endpoint had the following attribute:
[Authorize(AuthenticationSchemes = "Bearer")]
However, we want the API to be authenticated using both Identity Cookies as well as Bearer tokens, as there is a swagger UI for querying the API when logged in.
Using just the [Authorize] attribute will allow it to be accessed via cookies, but not access tokens through Postman (401)
Not sure if this is solved but what i personally done to enable API to be authenticated via cookie and bearer token is implement both schemes:
e.g.
services.AddAuthentication()
.AddCookie(options => {
options.LoginPath = "/Account/Unauthorized/";
options.AccessDeniedPath = "/Account/Forbidden/";
})
.AddJwtBearer(options => {
options.Audience = "http://localhost:5001/";
options.Authority = "http://localhost:5000/";
});
In the api controller i use [Authorize(AuthenticationSchemes = AuthSchemes)]
[Authorize(AuthenticationSchemes = AuthSchemes)]
public class TodoController: Controller
More details on different schemes and usages of them you can have a look here:
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?tabs=aspnetcore2x
Hope that helps.

Yammer.connect.embedFeed - starting a new conversation with a page URL that already belongs to another conversation

Here's the scenario: we're using yammer connect on an internal website for a specific team that has it's own yammer group. This website displays thousands of "items" and yammer is used to manage discussions about each item using the url (e.g. http://somesite/item?id=123). All was going well until someone in a different yammer group created a conversation and used a URL that points to a page in my internal website (e.g. http://somesite/item?id=123). So now when users go to that item they see a conversation for a different group.
I'm using the defaultGroupID in the config argument, but is there a way to tell yammer to ristrict it to a specific group id - and create a new conversation for that group only for the given URL (if one doesn't exist)? Can yammer even create multiple conversations in different groups regarding the same url - but keep the conversations restricted to each group?
Here's a code sample of how I'm creating the conversations:
var varYammerTargetUrl = $('#hidYammerTargetUrl').val();
var varYammerGroupId = $('#hidYammerGroupId').val();
var objProps = { url: varYammerTargetUrl, type: 'page', title: document.title };
yam.connect.embedFeed({
container: '#yammerFeed', network: 'mycompany.com', feedType: 'open-graph', feedId: '',
config: {
use_sso: false
, header: false
, footer: false
, showOpenGraphPreview: false
, defaultToCanonical: false
, hideNetworkName: false
, defaultGroupId: varYammerGroupId
, promptText: 'Start a conversation about this concession'
}
, objectProperties: objProps
});
When URLs are posted into Yammer there is an open graph object created. These objects don't live in groups by design and thus can be created anywhere within Yammer and are not restricted to certain groups.
Here is some additional documentation.

Resources