I use is4aspid template for IdentityServer4. When the server receives a request connect/authorize, called AccountController.Login. Question. Where i might see all default routing? Example for logout? And can i change this? Example, when receives a "connect/authorize", called OtherControl.MyLogin?
Where i might see all default routing?
There is a discovery endpoint that is used to retrieve metadata about your IdentityServer.
It returns information like the issuer name, key material, supported scopes etc... and you can see the endpoints (token endpoint, userinfo endpoint, etc) there.
When you run your IdentityServer application the discovery endpoint is available via /.well-known/openid-configuration relative to the base address, e.g.:
http://youridentityserver/.well-known/openid-configuration
Example for logout?
"end_session_endpoint": "http://youridentityserver/connect/endsession",
And can i change this?
You cannot change the discovery endpoint URL, it is according on the spec.
Edit
But where can I read the official documentation about this? And can I change this behavior?
Researching about the documentation I could found that you can use the UserInteraction options to reconfigure the routes (I agree that it should be better documented).
It means that you can set your own url (for LoginUrl, LogoutUrl, ConsentUrl, ErrorUrl) to redirect the user.
For example:
I developed a .Net Core application that redirects the user to a /Account/Login route according with the default identityserver4 configuration.
I want to redirect the user to Test/NewLogin route for user login. So, using the UserInteraction I can reconfigure the LoginUrl the in Startup.cs class.
Solution 1: Adding in a SetupIdentityServer options method
public void ConfigureServices(IServiceCollection services)
{
IIdentityServerBuilder builder = services.AddIdentityServer(SetupIdentityServer)
...
}
Below is the implementation of the SetupIdentityServer method:
private static void SetupIdentityServer(IdentityServer4.Configuration.IdentityServerOptions identityServerOptions)
{
identityServerOptions.UserInteraction.LoginUrl = "/Test/NewLogin";
}
Solution 2: I can achieve the same result with this code
public void ConfigureServices(IServiceCollection services)
{
IIdentityServerBuilder builder = services.AddIdentityServer(options => options.UserInteraction.LoginUrl = "/Test/NewLogin"))
...
}
Result:
I hope you've found your answer after two years but if anyone else is still looking, the default route paths are given in IdentityServer4.Constants - source here https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/src/Constants.cs
This class contains classes called UIConstants, EndpointNames and ProtocolRoutePaths which list the URI paths involved.
Unfortunately this still doesn't directly tell you which controller actions these paths map to but it might get you a bit closer to the truth:
public static class UIConstants
{
// the limit after which old messages are purged
public const int CookieMessageThreshold = 2;
public static class DefaultRoutePathParams
{
public const string Error = "errorId";
public const string Login = "returnUrl";
public const string Consent = "returnUrl";
public const string Logout = "logoutId";
public const string EndSessionCallback = "endSessionId";
public const string Custom = "returnUrl";
public const string UserCode = "userCode";
}
public static class DefaultRoutePaths
{
public const string Login = "/account/login";
public const string Logout = "/account/logout";
public const string Consent = "/consent";
public const string Error = "/home/error";
public const string DeviceVerification = "/device";
}
}
public static class EndpointNames
{
public const string Authorize = "Authorize";
public const string Token = "Token";
public const string DeviceAuthorization = "DeviceAuthorization";
public const string Discovery = "Discovery";
public const string Introspection = "Introspection";
public const string Revocation = "Revocation";
public const string EndSession = "Endsession";
public const string CheckSession = "Checksession";
public const string UserInfo = "Userinfo";
}
public static class ProtocolRoutePaths
{
public const string ConnectPathPrefix = "connect";
public const string Authorize = ConnectPathPrefix + "/authorize";
public const string AuthorizeCallback = Authorize + "/callback";
public const string DiscoveryConfiguration = ".well-known/openid-configuration";
public const string DiscoveryWebKeys = DiscoveryConfiguration + "/jwks";
public const string Token = ConnectPathPrefix + "/token";
public const string Revocation = ConnectPathPrefix + "/revocation";
public const string UserInfo = ConnectPathPrefix + "/userinfo";
public const string Introspection = ConnectPathPrefix + "/introspect";
public const string EndSession = ConnectPathPrefix + "/endsession";
public const string EndSessionCallback = EndSession + "/callback";
public const string CheckSession = ConnectPathPrefix + "/checksession";
public const string DeviceAuthorization = ConnectPathPrefix + "/deviceauthorization";
public const string MtlsPathPrefix = ConnectPathPrefix + "/mtls";
public const string MtlsToken = MtlsPathPrefix + "/token";
public const string MtlsRevocation = MtlsPathPrefix + "/revocation";
public const string MtlsIntrospection = MtlsPathPrefix + "/introspect";
public const string MtlsDeviceAuthorization = MtlsPathPrefix + "/deviceauthorization";
public static readonly string[] CorsPaths =
{
DiscoveryConfiguration,
DiscoveryWebKeys,
Token,
UserInfo,
Revocation
};
}
Related
I'm not a developer and we don't have one currently on our staff. So I looked all over the web and modified this Apex Class to suit my needs so that when an opportunity is marked as closed/won I get a small message in Slack.
It works great and I'd like to send this to Production. However, I didn't realize that I need to include a test for this and I am stuck on what that means.
Here's my Apex Class:
public with sharing class SlackPublisher {
private static final String SLACK_URL = 'https://hooks.slack.com/services/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';
public class Oppty {
#InvocableVariable(label='Opportunity Name')
public String opptyName;
#InvocableVariable(label='Opportunity Owner')
public String opptyOwnerName;
#InvocableVariable(label='Account Name')
public String acctName;
#InvocableVariable(label='Amount')
public String amount;
}
public class UrlMethods {
String BaseUrl; // The Url w/o the page (ex: 'https://na9.salesforce.com/')
String PageUrl; // The Url of the page (ex: '/apex/SomePageName')
String FullUrl; // The full Url of the current page w/query string parameters
// (ex: 'https://na9.salesforce.com/apex/SomePageName?x=1&y=2&z=3')
String Environment; // Writing code that can detect if it is executing in production or a sandbox
// can be extremely useful especially if working with sensitive data.
public UrlMethods() { // Constructor
BaseUrl = URL.getSalesforceBaseUrl().toExternalForm(); // (Example: 'https://na9.salesforce.com/')
}
}
#InvocableMethod(label='Post to Slack')
public static void postToSlack ( List<Oppty> opps ) {
Oppty o = opps[0]; // bulkify the code later
Map<String,Object> msg = new Map<String,Object>();
msg.put('text','Deal ' + o.opptyName + ' was just Closed/Won' + ':champagne:' + '\n' + 'for a total of ' + '$' + o.amount);
msg.put('mrkdwn', true);
String body = JSON.serialize(msg);
System.enqueueJob(new QueueableSlackPost(SLACK_URL, 'POST', body));
}
public class QueueableSlackPost implements System.Queueable, Database.AllowsCallouts {
private final String url;
private final String method;
private final String body;
public QueueableSlackPost(String url, String method, String body) {
this.url = url;
this.method = method;
this.body = body;
}
public void execute(System.QueueableContext ctx) {
HttpRequest req = new HttpRequest();
req.setEndpoint(url);
req.setMethod(method);
req.setBody(body);
Http http = new Http();
HttpResponse res = http.send(req);
}
}
}
and what I found online as a base for a test was this:
#isTest
private class SlackOpportunityPublisherTest {
private class RestMock implements HttpCalloutMock {
public HTTPResponse respond(HTTPRequest req) {
String fullJson = 'your Json Response';
HTTPResponse res = new HTTPResponse();
res.setHeader('Content-Type', 'text/json');
res.setBody(fullJson);
res.setStatusCode(200);
return res;
}
}
static testMethod void service_call() {
Test.setMock(HttpCalloutMock.class, new RestMock());
Test.startTest();
//your webserive call code
Database.GetUpdatedResult r =
Database.getUpdated(
'amount',
Datetime.now().addHours(-1),
Datetime.now());
Test.StopTest();
}
}
When I try to validate this in production it says it only gives me 68% coverage and I need 75%. Can someone help me write the test so that I can put into Prod?
Hello all I am using React JS as UI.
Firstly Assume I have designed form from where I am getting below 14 values
file
isSameMail
Student_firstName
Student_lastName
Student_Email
Student_details1
Student_details2
Student_details3
Student_details4
Student_details5
Student_details6
Student_details7
Student_details8
Student_details9
I am passing above values of form in Springboot API(registerStudent) using #RequestParam to register a student calling API from UI like
let data = new FormData();
data.append('file', this.state.file);
data.append('isSameMail', false);
data.append('Student_firstName', fields.firstName);
data.append('Student_lastName', fields.lastName);
data.append('Student_Email', fields.email);
data.append('Student_details1', fields.details1);
data.append('Student_details2', fields.details2);
data.append('Student_details3', fields.details3);
data.append('Student_details4', fields.details4);
data.append('Student_details5', fields.details5);
data.append('Student_details6', fields.details6);
data.append('Student_details7', fields.details7);
data.append('Student_details8', fields.details8);
data.append('Student_details9', fields.details9);
const url = http://localhost:8084/student/registerStudent';
fetch(url, {
method: 'POST',
body: data,
})
.then(res => {
if (res.ok) {
return res.json();
}
throw new Error(res.status);
})
.then(res => {
})
I am using spring-boot to call APIs were I am taking all the above values from form UI and storing it in the database(MYSQL)
Assume I am calling controller with API
#CrossOrigin
#PostMapping(path = "/student/registerStudent", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Object> registerStudent(#RequestParam(value = "file", required = false) MultipartFile UploadRegistrationForm,
#RequestParam("isSameMail") boolean isSameMail, #RequestParam("Student_firstName") String firstName,
#RequestParam("Student_lastName") String lastName, #RequestParam("Student_Email") String email,
#RequestParam("Student_details1") String details1, #RequestParam("Student_details2") String details2,
#RequestParam(value = "Student_details3", required = false) Double details3,
#RequestParam(value = "Student_details4", required = false) Double details4,
#RequestParam(value = "Student_details5", required = false) Double details5,
#RequestParam(value = "Student_details6", required = false) Double details6,
#RequestParam(value = "Student_details7", required = false) Date details7,
#RequestParam(value = "Student_details8", required = false) Integer details8,
#RequestParam(value = "Student_details9", required = false) String details9) throws IOException {
}
Now as you can see there are many parameters in registerStudent() method , but as I don't want to have parameters more than 7 parameters what is the appropriate way to use #RequestParam other than defining RequestParam several times.
Note: we are getting 1 multipart file.
If i understand you correctly, this may be the solution for you.
You can create an Object, which will contain all fields, and it will be look greate in controller method.
Something like this:
package com.example;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotNull;
import java.util.Date;
#NoArgsConstructor
#AllArgsConstructor
#Data
public class RegisterStudentReqestedParams {
private MultipartFile UploadRegistrationForm;
#NotNull
private boolean isSameMail;
#NotNull
private String firstName;
#NotNull
private String lastName;
#NotNull
private String email;
#NotNull
private String details1;
#NotNull
private String details2;
private Double details3;
private Double details4;
private Double details5;
private Double details6;
private Date details7;
private Integer details8;
private String details9;
}
And the controller now looks like this:
#CrossOrigin
#PostMapping(path = "/student/registerStudent", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Object> registerStudent(#RequestBody #Valid RegisterStudentReqestedParams registerStudentReqestedParams){}
I am using the extension:
services.AddOidcStateDataFormatterCache();
in Asp.Net Core, to store the state in the distributed cache which is implemented using Redis:
services.AddStackExchangeRedisCache(options => {
options.Configuration = Configuration[RedisConnection];
});
but it seems that the entries in the Redis cache are not set with TTL:
Is there a setting to control the TTL of the keys that get created in the cache?
Already reported. Waiting for response. (Please, mention that you need this, there too!)
For the moment we use an ugly inheritor. Ugly because the base has no virtual methods and in addition requires a helper internal class ConfigureOpenIdConnectOptionsTTL : IPostConfigureOptions<OpenIdConnectOptions> (mostly copy&paste again) but at least it fixed "slow redis in production".
public class DistributedCacheStateDataFormatterTTL:
DistributedCacheStateDataFormatter, ISecureDataFormat<AuthenticationProperties>
{
public static readonly TimeSpan DefaultCacheDuration = TimeSpan.FromMinutes(5);
private readonly IHttpContextAccessor _httpContext;
private readonly string _name;
public DistributedCacheStateDataFormatterTTL(
IHttpContextAccessor httpContext, string name) : base(httpContext, name)
{
_httpContext = httpContext;
_name = name;
}
private string CacheKeyPrefix => "DistributedCacheStateDataFormatter";
private IDistributedCache Cache =>
_httpContext.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();
private IDataProtector Protector =>
_httpContext.HttpContext.RequestServices
.GetRequiredService<IDataProtectionProvider>()
.CreateProtector(CacheKeyPrefix, _name);
string ISecureDataFormat<AuthenticationProperties>
.Protect(AuthenticationProperties data)
{
return ((ISecureDataFormat<AuthenticationProperties>)this).
Protect(data, string.Empty);
}
string ISecureDataFormat<AuthenticationProperties>
.Protect(AuthenticationProperties data, string purpose)
{
var key = Guid.NewGuid().ToString();
var cacheKey = $"{CacheKeyPrefix}-{purpose}-{key}";
var json = JsonConvert.SerializeObject(data, new JsonSerializerSettings()
{
DefaultValueHandling = DefaultValueHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
});
var options = new DistributedCacheEntryOptions();
if (data.ExpiresUtc.HasValue)
options.SetAbsoluteExpiration(data.ExpiresUtc.Value);
else
options.SetSlidingExpiration(DefaultCacheDuration);
// Rather than encrypt the full AuthenticationProperties
// cache the data and encrypt the key that points to the data
Cache.SetString(cacheKey, json, options);
return Protector.Protect(key);
}
}
internal class ConfigureOpenIdConnectOptionsTTL : IPostConfigureOptions<OpenIdConnectOptions>
{
private string[] _schemes;
private readonly IHttpContextAccessor _httpContextAccessor;
public ConfigureOpenIdConnectOptionsTTL(string[] schemes, IHttpContextAccessor httpContextAccessor)
{
_schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
public void PostConfigure(string name, OpenIdConnectOptions options)
{
// no schemes means configure them all
if (_schemes.Length == 0 || _schemes.Contains(name))
{
options.StateDataFormat = new DistributedCacheStateDataFormatterTTL(_httpContextAccessor, name);
}
}
public static IServiceCollection AddOidcStateDataFormatterCache(
IServiceCollection services,
params string[] schemes)
{
services.RemoveAll<IPostConfigureOptions<OpenIdConnectOptions>>();
services.AddSingleton<IPostConfigureOptions<OpenIdConnectOptions>>(
svcs => new ConfigureOpenIdConnectOptionsTTL(
schemes,
svcs.GetRequiredService<IHttpContextAccessor>())
);
return services;
}
}
I am working on Web API with AngularJS. I had implemented Web API token mechanism few days ago and able to login the application using the access token. I have used external DB table instead of ASP.NET identity table to authorize user.
I want to store user information in class so that it can be accessed easily from different controllers after User logged in. Currently I am using ClaimsIdentity in Controller Class to get the user information.
UserIdentityViewModel.cs
public class UserIdentityViewModel
{
public string UserName { get; set; }
public Guid UserId { get; set; }
}
Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
var myProvider = new AuthorizationServerProvider();
OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = myProvider
};
app.UseOAuthAuthorizationServer(options);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
AuthorizationServerProvider.cs
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated(); //
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
string userId = context.UserName;
string password = context.Password;
EmployeeAccessBLL chkEmpAccessBLL = new EmployeeAccessBLL();
EmployeeAccessViewModel vmEmployeeAccess = chkEmpAccessBLL.CheckEmployeeAccess(Convert.ToInt32(userId), password);
if(vmEmployeeAccess != null)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("username", vmEmployeeAccess.EmpName));
identity.AddClaim(new Claim("userid", Convert.ToString(vmEmployeeAccess.EmployeeId)));
UserIdentityViewModel vmUser = new UserIdentityViewModel();
vmUser.UserId = vmEmployeeAccess.EmployeeId;
vmUser.UserName = vmEmployeeAccess.EmpName;
context.Validated(identity);
}
else
{
context.SetError("invalid_grant", "Provided username and password is incorrect");
return;
}
}
}
EventController.cs
public class StreamEventController : ApiController
{
[Authorize]
[Route("api/addevent")]
[HttpPost]
public List<string> AddEvent(StreamEventViewModel vmEvent)
{
//Able to get User Information from Identity.Claims
var identity = (ClaimsIdentity)User.Identity;
string userId = identity.Claims
.Where(c => c.Type == "userid")
.Select(c => c.Value).FirstOrDefault();
//Not able to get User Information from following as new object instance gets created
UserIdentityViewModel vmUser = new UserIdentityViewModel();
vmEvent.CreatedBy = vmUser.UserId;
vmEvent.ModifiedBy = vmUser.UserId;
}
}
Instead of writing "Identity.Claims" in each method of every controller I want to use simple get/set approach or any other methodology to get User Information . The use of Static class is also bad in my opinion as it will store one information of user and multiple user login information gets missed.
Please help me and share with me the best approach that has been used in other Web API projects for login.
You can add a private variable which will be set in the constructor of the controller, like this:
// Should only be used in protected methods.
private ClaimsIdentity ThisUser = null;
public MyController()
{
if (User.Identity.IsAuthenticated)
ThisUser = (ClaimsIdentity)User.Identity;
}
[Authorize]
[Route("api/addevent")]
[HttpPost]
public List<string> AddEvent(StreamEventViewModel vmEvent)
{
string userId = ThisUser.FindFirstValue("userid");
}
Or create a User class where you load all properties:
private UserClass ThisUser = null;
public MyController()
{
if (User.Identity.IsAuthenticated)
ThisUser = new UserClass(User);
}
[Authorize]
[Route("api/addevent")]
[HttpPost]
public List<string> AddEvent(StreamEventViewModel vmEvent)
{
string userId = ThisUser.UserId;
}
Where UserClass is something like:
public class UserClass
{
public string UserId { get; private set; }
public UserClass(IPrincipal user)
{
UserId = user.FindFirstValue("userid");
}
}
But this is just overhead for the same thing.
You can consider to move things to an extension. In that case you get something like:
public static class RequestExtensions
{
public static UserClass GetUser(this HttpRequestMessage request)
{
return new UserClass(request.GetOwinContext().Authentication.User);
}
public static ClaimsIdentiy GetUser2(this HttpRequestMessage request)
{
return new (ClaimsIdentity)request.GetOwinContext().Authentication.User;
}
}
Which you can call:
[Authorize]
[Route("api/addevent")]
[HttpPost]
public List<string> AddEvent(StreamEventViewModel vmEvent)
{
string userId = Request.GetUser.UserId;
string userId2 = Request.GetUser2.FindFirstValue("userid");
}
I think I would go for Request.GetUser2.FindFirstValue("userid");
The code is meant to give you an idea. I didn't test the code but I think it should work.
I am creating a small test. In Code behind I have two classes. Pages, LoginPage.
The first part is running. I dont know how to integrate with second part. Currently I am able to open the browser. Also I am trying to use the Page obect model pattern .
Fitnesse code
!|import|
|TestFramework|
!|script|Pages|
|Goto||https://gmail.com|
|LoginPage|CheckRequiredElementsPresent|Pass|
Fixtures
public class Pages
{
string url;
private LoginPage loginPage;
public static void Goto(string url)
{
Browser.Goto(url);
}
}
public class LoginPage
{
static string PageTitle;
[FindsBy(How = How.Id, Using = "TextUsername")]
private static IWebElement username;
[FindsBy(How = How.Id, Using = "TextPassword")]
private static IWebElement password;
[FindsBy(How = How.Id, Using = "_ButtonLogin")]
private static IWebElement submit;
public string IsAtLoginPage()
{
return "";
}
public string CheckRequiredElementsPresent()
{
if (username != null && password != null && submit != null)
{
return "Pass";
}
return "Fail";
}
}
}
You need to do something like below:
Fitnesse Code
!|import|
|TestFramework|
!|script|Pages|
|Goto||https://gmail.com|
|check Required Element|Pass|
You need to call your second class from your Pages class, please see the code changes & fitnesse fixture changes that I've made.
Fixtures
public class Pages
{
string url;
private LoginPage loginPage;
public static void Goto(string url)
{
Browser.Goto(url);
}
// This is what you need to do to refer method of second class.
// This method will be called after Goto method in sequence.
public boolean checkRequiredElement(){
return loginPage.CheckRequiredElementsPresent()
}
}