SharePoint Search REST API with bearer token returns wrong number of items - rest

I have an EXTREMELY strange issue with the search.
I am doing a query by using a GET on
https://sonar-sandbox.gredspdev.loc/_api/search/query?querytext='DMSSonarDocId:5042aa1f-b3a4-4577-8e21-8a47ca27c243 OR DMSSonarDocId:1401144b-bd3d-429a-a386-5061ecc714e1'&sourceid='a0f4d450-e701-4f2a-888a-8d871002752d'&trimduplicates=false&rankingmodelid='05289DBE-73E9-4665-BF69-EE68274176EB'&rowlimit=9000&enablestemming=false&enablesorting=false&selectproperties='DMSSonarDocId,<...>'
I am authenticating using a bearer token generated for my user. This query returns 7 items. Then I am executing THE SAME URL in my browser with my user (NTLM) and it returns 10 items. That is not all. I generate the token for my user one more time. Paste it to the previous GET request with a bearer token and it returns 10 items... I am waiting few seconds, lets say 30... GET one more time and I have 7 items returned (always the same)! And this is 100% replicable. After another GET from the browser and regeneration of the token 10 items, after some time on the same token 7 items....
Update. I have found difference in logs in ULS:
When working correct:
Context has no SMTP/UPN claims. IdentityContext: '{"nameid":"s-1-5-21-2843295230-2675739751-2774624307-1482","nii":"urn:office:idp:activedirectory","upn":"kowalj#spdev.loc","userId":"0#.w|spdev\\kowalj","appliesTo":"https:\/\/sonar-sandbox.spdev.loc\/"}'
When not working correct:
Context has no SMTP/UPN claims. IdentityContext: '{"nameid":"s-1-5-21-2843295230-2675739751-2774624307-1482","nii":"urn:office:idp:activedirectory","upn":"spdev\\kowalj","userId":"0#.w|spdev\\kowalj","appliesTo":"https:\/\/sonar-sandbox.spdev.loc\/"}'
ANOTHER FINDINGS:
Missing items are those which are assigned to me directly - not through group resolved by our custom claims provider - yes, we have a custom claims provider which worked ok for a long time (we were using only NTLM authorization).
We are sending those claims:
new Claim[]
{
new Claim("nameid", sid),
new Claim("nii", Constants.Auth.Token.IdentityIssuer)
};
ANOTHER FINDINGS:
When everything work correctly, executing this code in the SP farm solution in some REST proxy: ((ClaimsIdentity)HttpContext.Current.User?.Identity).Claims.FirstOrDefault(c => c.ClaimType.EqualsIgnoreCase(ClaimTypes.Upn)) returns upn.
When the search is not working, the same code returns null... And as I said, I can refresh the page and at the beginning the upn is there and after some time it is not...

I have found a work around. Not very good but I do not see any other option for now.
We have started Claims to Windows Token Service and if user does some requests to our app, we do (from time to time) requests to our custom proxy placed in the SharePoint farm solution to simulate using SharePoint by that user by using normal windows authentication:
public void RefreshUpn()
{
WindowsImpersonationContext _wic = null;
try
{
string login = HttpContext.Current.User.Identity.Name;
login = login.Substring(login.LastIndexOf('|') + 1);
string[] loginParts = login.Split('\\');
string loginForUpnLogon = Culture.Invariant($"{loginParts[1]}#{loginParts[0]}");
WindowsIdentity wi = S4UClient.UpnLogon(loginForUpnLogon);
if(wi == null)
{
throw new InvalidOperationException(Culture.Invariant($"Could not impersonate user '{HttpContext.Current.User.Identity.Name}'."));
}
_wic = wi.Impersonate();
using (var wc = new WebClient())
{
wc.UseDefaultCredentials = true;
var requestUrl = HttpContext.Current.Request.Url;
wc.DownloadString(requestUrl.Scheme + "://" + requestUrl.Host + "/_api/web/currentuser");
}
}
finally
{
_wic?.Undo();
}
}
After such request, SharePoint responses to us correctly for around 150 seconds.

Related

Azure Devops REST API 203 returned

When users signup for our application, we ask them to go through the oAuth process. We ask them for their organisation first, so we can use the access token we receive during oauth to make requests.
Here is a sample of the code we use to do that:
var client = new RestClient();
var createItemRequest = new RestRequest("https://app.vssps.visualstudio.com/oauth2/token", Method.POST);
string body = $"client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&" +
$"client_assertion={HttpUtility.UrlEncode(clientSecret)}&grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" +
$"&assertion={HttpUtility.UrlEncode(code)}&redirect_uri={redirectUrl}";
var formData = Encoding.UTF8.GetBytes(body);
createItemRequest.AddParameter("application/x-www-form-urlencoded", formData, ParameterType.RequestBody);
var accessTokenResponse = client.Execute(createItemRequest).Content;
_logger.LogInformation($"accessToken response: {accessTokenResponse}");
var accessTokenObject = JsonConvert.DeserializeObject<AccessTokenResponse>(accessTokenResponse);
if (accessTokenObject != null
&& !string.IsNullOrEmpty(accessTokenObject.AccessToken)
&& !string.IsNullOrEmpty(accessTokenObject.RefreshToken))
{
_logger.LogInformation($"We have access token and refresh");
}
We always manage to get the refresh + access tokens for every user. Directly after this call we make a "get all projects call" to test the connection and tokens. For some users signing up when we make this call to check we can see them, some users receive a 203 response when we use the access token to make the first call (to get a list of projects). The request looks like:
The HTML returns:
My question is, why do some users receive a 203 and some not? We have NO idea of what to tell the user when this happens - what do they check? Why does this happen? And how can they fix it?

Websphere Commerce Custom REST service for Login using social sign in not generating valid WC Tokens and CTXMGMT table not getting updated

In the current website, social login is implemented using the mapping in struts and it will call the custom controller command "XYZThirdPartyLoginCmdImpl" which will authenticate the details passed and it will call the out of the box "LogonCmd" for login.
For creating a REST service for the above functinality, created a custom REST handler " XYZThirdPartyLoginHandler" and from there called the existing command "XYZThirdPartyLoginCmdImpl" using the method executeControllerCommandWithContext. Once the response is generated, WCToken and WCTrustedToken is generated by the below code.
ActivityToken token = getActivityToken();
String identitySignature = token.getSignature();
String identityId = token.getActivityGUID().getGUID().toString();
Map<String, Object> identityTokenInfo = new HashMap();
identityTokenInfo.put(MemberFacadeConstants.EC_USERID, new String[] { userId.toString() } );
identityTokenInfo.put(MemberFacadeConstants.ACTIVITY_TOKEN_ID, new String[] { identityId } );
identityTokenInfo.put(MemberFacadeConstants.ACTIVITY_TOKEN_SIGNATURE, new String[] { identitySignature } );
Map<String, String> commerceTokens = CommerceTokenHelper.generateCommerceTokens(identityTokenInfo);
String wcToken = commerceTokens.get(CommerceTokenHelper.WC_TOKEN);
String wcTrustedToken = commerceTokens.get(CommerceTokenHelper.WC_TRUSTED_TOKEN);
The tokens generated using this is not valid. If we try to invoke any other rest service using this token it shows invalid user session error. "XYZThirdPartyLoginCmdImpl" authentication is success as the userId returned is correct. After executing this the user context is not getting created in CTXMGMT table.
Please guide on how to generate the valid tokens in REST flow in this use case.
If you are on v9, you might want to investigate the oauth_validate REST call (/wcs/resources/store//loginidentity/oauth_validate). See the KC article for more information: [https://www.ibm.com/support/knowledgecenter/SSZLC2_9.0.0/com.ibm.commerce.integration.doc/tasks/tcv_sociallogin.htm][1]. This calls some different commands (OAuthTokenValidationCmdImpl and OpenUserRegisterCmd) than what you might be using, but it allows you to pass in a 3rd party token, and it generates the right tokens.

How to get the last login session details of a user in Keycloak using Keycloak rest endpoints?

How to get the last login session details of a user in Keycloak using keycloak rest endpoints?
Example:
builder.append(OAuth2Constants.AUDIENCE+"="+clientId+"&");
builder.append(OAuth2Constants.GRANT_TYPE+"="+OAuth2Constants.UMA_GRANT_TYPE+"&");
headers.put("Content-Type", "application/x-www-form-urlencoded");
headers.put("Authorization", "Bearer "+accessToken);
//String keycloakURL = keyCloakCFGBean.getCreateRefreshSession();
String keycloakURL="http://10.10.8.113:10004/auth/realms/{realm}/protocol/openid-connect/token";
keycloakURL = keycloakURL.replace("{realm}", realmName);
URL url = new URL(keycloakURL);
httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setUseCaches(false);
httpURLConnection.setDoInput(true);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setDoOutput(true);
if (headers != null && headers.size() > 0) {
Iterator<Entry<String, String>> itr = headers.entrySet().iterator();
while (itr.hasNext()) {
Entry<String, String> entry = itr.next();
httpURLConnection.setRequestProperty(entry.getKey(), entry.getValue());
}
}
outputStreamWriter = new OutputStreamWriter(httpURLConnection.getOutputStream(), StandardCharsets.UTF_8);
outputStreamWriter.write(builder.toString());
outputStreamWriter.flush();
So there are a couple of scenarios here. All of this information assumes that you have an appropriate bearer token that you are sending in the header of the request for authentication/authorisation, and requires that you have sufficient admin privileges in the Keycloak realm.
I've not gone into detail in terms of the precise code you write in a particular language, but hopefully the instructions are clear in terms of what you need your code to do.
Sessions
If you are interested in ACTIVE user sessions specifically, you can use the API endpoint as described at: https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_getsessions
That is:
GET /{realm}/users/{id}/sessions
e.g. the full URL would be:
https://{server}/auth/admin/realms/{realm}/users/{id}/sessions
In the response there will be a property called lastAccess that will contain a number that is the usual UNIX milliseconds since 1/1/1970. If you take that number, you can then parse it in your language of choice (Java from the looks of it?) to get the date/time in the format that you require.
All Logins
However I suspect what you really want is to look at the last login across all of the stored information in Keycloak, not just active user sessions, so for that you need to look for the Realm EVENTS. Note that Keycloak only stores events for a certain amount of time, so if it's older than that then you won't find any entries. You can change how long events are stored for in the events config page of the realm admin console.
To get all realm events you call the endpoint mentioned here: https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_getevents (Search for "Get events Returns all events, or filters them based on URL query parameters listed here" if the link doesn't take you straight there).
i.e.
GET /{realm}/events
e.g. the full URL would be: https://{server}/auth/admin/realms/{realm}/events
You will need to filter the results based on "type" (i.e. so that you only have events of type "LOGIN"), and if you want to check a specific user you would also want to filter the results on userId based on the ID of that user account.
You can perform both of these filters as part of the request, to save you having to get the full list of events and filter it client-side. To filter in the request you do something like the following:
https://{server}/auth/admin/realms/{realm}/events?type=LOGIN&user={id}
From the resultant JSON you can then get the result with the highest value of the time property, that represents that login event. The time property will be a UNIX time of milliseconds since 1/1/1970 again, so again you can convert this to a format that is appropriate to you once you have it.
Hope that's helpful!
use Keycloak rest Api
${keycloakUri}/admin/realms/${keycloakRealm}/users
and you will get a response as JWT. Decode it and you will get all the info related to the user.
OR you may use the java client API for example by
Keycloak kc = KeycloakBuilder.builder()
.serverUrl("https://localhost:8443/auth")
.realm("master")
.username("admin")
.password("admin")
.clientId("Mycli")
.resteasyClient(new ResteasyClientBuilder().connectionPoolSize(10).build())
.build();
CredentialRepresentation credential = new CredentialRepresentation();
credential.setType(CredentialRepresentation.PASSWORD);
credential.setValue("test123");
UserRepresentation user = new UserRepresentation();
user.setUsername("testuser2");
user.setFirstName("Test2");
user.setLastName("User2");
user.setEmail("aaa#bbb.com");
user.setCredentials(Arrays.asList(credential));
user.setEnabled(true);
user.setRealmRoles(Arrays.asList("admin"));
UsersResource usersResource = kc.realm("my-realem").users();
UserResource userResource = usersResource.get("08afb701-fae5-40b4-8895-e387ba1902fb");
you will get the list of users. Filter by user ID then you will find all user info.

Spring remember-me with MongoDB does not delete tokens

I followed this tutorial to implement remember-me functionality with MongoDB.
The tokens are saved in the database when i click the rememberme checkbox in the login page. If i delete the db entry manually and the cookie JSESSIONID maxage has expired i am getting logged out and if the JSESSIONID has expired and the remember-me cookie does not, i am still logged in which is great.
All works well but i have a question. The removeUserTokens function is never called, should i manually delete the token entry from the database? If yes where should i implement this?
Thank you.
It has to be deleted manually (e.g. by batch process) per Java doc.
PersistentTokenBasedRememberMeServices
Note that while this class will use the date a token was created to
check whether a presented cookie is older than the configured
tokenValiditySeconds property and deny authentication in this case, it
will not delete these tokens from storage. A suitable batch process
should be run periodically to remove expired tokens from the database.
The abstraction (PersistentTokenRepository) used by PersistentTokenBasedRememberMeServices to store the persistent login tokens for a user.
After searching it a bit more i found that when i logout and having this to my configuration:
http.authorizeRequests().antMatchers("/signup", "/about").permitAll().antMatchers("/doctor/**")
.hasRole("DOCTOR").anyRequest().authenticated().and().rememberMe().rememberMeParameter("remember-me")
.tokenRepository(tokenRepository).tokenValiditySeconds(1209600).and().formLogin().loginPage("/login")
.failureUrl("/login?error=true").permitAll().and().logout().logoutUrl("/logout")
.deleteCookies("JSESSIONID").invalidateHttpSession(true).logoutSuccessUrl("/login").permitAll();....
the removeUserTokens method is called and the associated token is deleted from the db. I think the trick is made by:
.logout().logoutUrl("/logout")
.deleteCookies("JSESSIONID").invalidateHttpSession(true)
Also as notionquest said above i added a Spring cron job to run every Friday at 3 AM in case of something is left in the db.
#Scheduled(cron = "0 0 3 * * FRI")
public void doScheduledWork() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.WEEK_OF_MONTH, -2);
tokenRepository.deleteBeforeDated(calendar.getTime());
logger.info("INFO", "Cron job runed at " + new Date() + " until " + calendar.getTime() + " !");
}

Firebase: Authenticate an existing user using REST API and Firebases hidden Auth URL

For the past 3 years we have used HTML/Js only with Firebase but now we are using Unity as well.
The current Unity/Firebase only works on Android/iOS when deployed and 99% of our work is on the windows store.
I've actually got a pretty decent Unity/Firebase codebase going but it requires me to use a full App Secret.
All the other libraries expose a method to login with Email/Password but the REST API only allows the use of a token or your app secret that it then states is ill advised to put into your client; I guess the thinking is if you're using a different library that you'll have your own auth/user method which we don't...
Now, I've pulled apart the web version and got this:
https://auth.firebase.com/v2/<myfirebase>/auth/password?&email=dennis%40<mysite>&password=<mypassword>v=js-2.2.9&transport=json&suppress_status_codes=true
So there IS an endpoint that I can send stuff to and I've tested it inside unity with good results.
Obviously the URL isn't guaranteed to stay working but I'm wondering if there is any reason NOT to use this?
Also, Why not just expose this endpoint in the official REST API?
As I understand it, that URL will continue to work for your Legacy Firebase project. You will have to do the same sort of reverse engineering if you want to update to the new Firebase 3.0 API. However, if you are still using a legacy Firebase project -- I encourage you to take a look at this. It has not been updated to work with Firebase 3.0 -- so I needed to do something similar to what you did to allow login to the new API.
I was able to do this with the new API using C# as follows (where FirebaseManager is a Singleton I wrote for Global variables and functions to write and read from/to the DB :
Hashtable loginData = new Hashtable();
loginData.Add ("email", <EMAIL-GOES-HERE>);
loginData.Add ("password", <PASSWORD-GOES-HERE>);
loginData.Add ("returnSecureToken", true);
UnityHTTP.Request loginRequest = new UnityHTTP.Request ("post",
"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key="
+ <YOUR-PROJECT-API-KEY-GOES-HERE>, loginData);
loginRequest.Send ((request) => {
Hashtable jsonResponse = (Hashtable)JSON.JsonDecode(request.response.Text);
if (jsonResponse == null) {
DisplayErrorMessage("Error logging in. Server returned null or malformed response");
}
FirebaseManager.Instance.idToken = (string)jsonResponse["idToken"]; // This is your auth token
FirebaseManager.Instance.uid = (string)jsonResponse["localId"]; // this is your "uid"
});
// I have a list of users in my db keyed by the "uid" -- I access them like this
UnityHTTP.Request fullnameRequest = new UnityHTTP.Request ("get",
<YOUR-DATABASE-ROOT-URL-HERE>
+ "/users/" + FirebaseManager.Instance.uid + ".json?auth=" + FirebaseManager.Instance.idToken);
fullnameRequest.Send ((request) => {
Debug.Log(request.response.Text);
Hashtable jsonResponse = (Hashtable)JSON.JsonDecode(request.response.Text);
if (jsonResponse == null) {
DisplayErrorMessage("Error getting user info. Server returned null or malformed response");
}
FirebaseManager.Instance.fullname = (string)jsonResponse["fullname"];
FirebaseManager.Instance.groupId = (string)jsonResponse["group"]; // just storing this in memory
});
So I don't think there is any harm in using the URL, just make sure you budget time for more work when things change.