temporary link / url for the attachments of an issue in jira cloud instance - jira

JIRA provides a way to access the attachments of an issue using basic auth, jwt auth mechanisms. Using which we can download those files. We're able to download the files using both authentication mechanisms.
sample jwt auth:
curl -X GET --url https://{site-name}.atlassian.net/secure/attachment/1001/example.txt -H 'Authorization: jwt '
Issue / Our requirement:
But is there a way to generate temporarily accessible url for the JIRA issue's attachments which will have token embedded into the URI itself. I've added the example of that below
example url:
https://{site-name}.atlassian.net/attachment/1001/example.txt?token={temp_access_token}
While accessing / clicking on the above mentioned url the download should automatically start even if the user isn't logged into their account
Reason for our requirement:
We're creating jira cloud based service / app and one of its feature is providing access to the user's attachments through our application. Our limitation(cloud service cost) is that we can't download all the huge sized attachments and store and manage it. So we're looking for a solution using which user's can download from the JIRA's server directly

In your JWT generation steps, you can define how long the JWT should be valid. And you can attach a JWT to the URL like this: <Jira Base Url>/rest/api/3/...?jwt=.... This way, you could generate a JWT on demand and it'll only be valid for the given time that you define.
In the Java Example on the page Understanding JWT for Connect apps you can see how they are setting the expirationTime. Just do the same, on demand. Here is the important part of the code snippet:
public class JWTSample {
public String createUriWithJwt()
throws UnsupportedEncodingException, NoSuchAlgorithmException {
long issuedAt = System.currentTimeMillis() / 1000L;
long expiresAt = issuedAt + 180L;
/* ... */
JwtJsonBuilder jwtBuilder = new JsonSmartJwtJsonBuilder()
.issuedAt(issuedAt)
.expirationTime(expiresAt)
.issuer(key);
/* ... */
String jwtToken = /* ... */;
String apiUrl = baseUrl + apiPath + "?jwt=" + jwtToken;
return apiUrl;
}
}
Security concern: I'm explicitly mentioning that you should generate these links on demand because you should not set the expiration date to more than 5-10 minutes (which is already quite high). Otherwise an attacker just needs to retrieve your generated link (URLs are often logged somewhere) and is also able to retrieve the attachment as well.
Alternative Approach
Since you mentioned you'll build a service/app, why not chain the attachment download through your service? This way you wouldn't have to expose a JWT which is a potential security threat. For example: you offer a download button in your UI, this sends a HTTP request to your service and your service downloads the attachment and then forwards it to your user. However, this would not comply with your requirement to give access to unauthenticated users - if that's really what you want to do.

Related

How does an OAuth2 server obtain launch context for smart on FHIR?

I am integrating a Smart on FHIR app that will be launched from within an EHR. When the user clicks a button to launch the app, we set a GUID and the current Patient ID to a database record on our FHIR server. The assumption being that given the 'Launch' scope, the OAuth server will call the appropriate API to retrieve the Patient ID given that the GUID is included in the url params.
The call to auth looks like this:
_clientID = {the unique client ID registered to our auth server}
_redirectURL = {redirect back to auth for eventual token request}
launch={the GUID value generated at start of the session and paired with the Patient ID}
_scopes = "launch patient/*.* openid profile"
state = {some opaque value}
aud = {the base URL for our FHIR server}
string url = $"{authorizeURL}?response_type=code&client_id={_clientID}&" +
$"redirect_uri={_redirectURL}&" +
$"launch={launch}&" +
$"scope={HttpUtility.UrlEncode(_scopes)}&" +
$"state={state}&" +
$"aud=https://xxx-smart.xxxxxxxxx.com";
All of this works and I end up with a json response that includes the id_token, access_token, expires_in, token_type('Bearer'). But, no 'patient'.
My assumption was that the OAuth server would call the scope 'launch/patient' on our FHIR server but no such call is being made. In fact, I created a few endpoints just for the purpose of logging and NONE of them are being called.
Here is an example of one of my FHIR Server test/log endpoints (I created few with 1 to 4 parameters):
[AllowAnonymous]
[HttpGet("{functionName}/{id}")]
public string GetPatientData3(string functionName, string id)
{
TelemetryClient telemetry = new();
telemetry.TrackEvent($"FHIR SVR GetPatientData3 {functionName} {id}");
string configJson = "0009998888";
return configJson;
}
How do I set this 'patient' context properly?
How does the OAuth server retrieve this context so I can have that patient ID appear in the json response from the ~/token call?
Further Notes:
The contents of the openid-configuration:
{"token_endpoint":
"https://aadproxy.azurewebsites.net/xxx/oauth2/v2.0/token",
"token_endpoint_auth_methods_supported":
["client_secret_post","private_key_jwt","client_secret_basic"],
"jwks_uri":
"https://login.microsoftonline.com/xxx/discovery/v2.0/keys",
"response_modes_supported": ["query","fragment","form_post"],
"subject_types_supported": ["pairwise"],
"id_token_signing_alg_values_supported": ["RS256"],
"response_types_supported":["code","id_token","code
id_token","id_token token"],
"scopes_supported":["openid","profile","email","offline_access"],
"issuer": "https://login.microsoftonline.com/xxx/v2.0",
"request_uri_parameter_supported":false,
"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo",
"authorization_endpoint":
"https://aadproxy.azurewebsites.net/xxx/oauth2/v2.0/authorize",
"device_authorization_endpoint":
"https://login.microsoftonline.com/xxx/oauth2/v2.0/devicecode",
"http_logout_supported":true,
"frontchannel_logout_supported":true,
"end_session_endpoint":
"https://login.microsoftonline.com/xxx/oauth2/v2.0/logout",
"claims_supported":
["sub","iss","cloud_instance_name","cloud_instance_host_name",
"cloud_graph_host_name","msgraph_host","aud","exp","iat",
"auth_time","acr","nonce","preferred_username",
"name","tid","ver","at_hash","c_hash","email"],
"kerberos_endpoint":
"https://login.microsoftonline.com/xxx/kerberos",
"tenant_region_scope":"NA",
"cloud_instance_name":"microsoftonline.com",
"cloud_graph_host_name":"graph.windows.net",
"msgraph_host":"graph.microsoft.com",
"rbac_url":"https://pas.windows.net"}
So, I notice that the 'patient/.' and the 'launch' scope among a whole host of others that I have are not supported according to my openid config. The only supported ones are "openid","profile","email", "offline_access".
In Azure AD, 'App Registration' > 'Expose an API' I have a list of at least 15 scopes entered there. In 'API' permissions they are all listed there as well.
One other thing to note, AzureAD does not handle scopes with a forward slash. So, launch/patient has to be entered as launch-patient. We also had to implement a proxy server to capture the ~/oauth2/v2.0/authorize request and modify the scope parameter entries to reflect this before passing on the request to the actual server.
I guess the pertinent question now is: How do the scopes that I have entered manually get supported?

How do you use an iOS Google ID Token to fetch a user's full profile from the backend (Node JS)?

I'm using the Google Login iOS SDK to login, then passing GIDGoogleUser.authentication.idToken to the server, which I'm then verifying in Node JS. The verification in the code below works fine. "payload" var ends up being correct with basic information about the user.
How do I translate the idToken into credentials that I can use to git the people.get endpoint? (I want to know whether the user is using the default Google profile photo or not, and that is available from the people.get endpoint.) This does not seem to be documented anywhere.
https://developers.google.com/people/api/rest/v1/people/get
var auth = new GoogleAuth;
var client = new auth.OAuth2(GoogleUtils.clientIDs, '', '');
client.verifyIdToken(
token,
GoogleUtils.clientIDs,
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3],
function(e, login) {
if (e) {
return next(e, null);
}
var payload = login.getPayload();
return next(null, payload);
});
Thanks for your help. I can't seem to find any of this info in the documentation. Google's APIs are very poorly documented it seems.
Unfortunately, as noted, the current ID token payload does not say whether the photo is the default one (probably something we should add). If you need an access token to call Google's REST APIs (such as people.get) for more user data, then you need to obtain an OAuth auth code, and exchange it for access and refresh tokens, as documented at https://developers.google.com/identity/sign-in/ios/offline-access

Google Oauth - TokenVerifier How to USE?

I'm trying to use Google OAuth with Sign in & Sign Up for my Web Server Application.
This is the page : https://developers.google.com/identity/sign-in/web/backend-auth that I have referenced, but I am stuck in using the Google Client API, the TokenVerifier that is mentioned below in the document. I tried to find some examples, but I couldn't find one, as I am not sure how to handle the parameters in the methods that the sample shows.
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
...
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Arrays.asList(CLIENT_ID))
.build();
// (Receive idTokenString by HTTPS POST)
GoogleIdToken idToken = verifier.verify(idTokenString);
if (idToken != null) {
Payload payload = idToken.getPayload();
if (payload.getHostedDomain().equals(APPS_DOMAIN_NAME)
// If multiple clients access the backend server:
&& Arrays.asList(ANDROID_CLIENT_ID, IOS_CLIENT_ID).contains(payload.getAuthorizedParty())) {
System.out.println("User ID: " + payload.getSubject());
} else {
System.out.println("Invalid ID token.");
}
} else {
System.out.println("Invalid ID token.");
}
For example, I know what these CLIENT_ID, ANDROID_CLIENT_ID, IOS_CLIENT_ID parameters mean in the sample code(in the reference page), but the server only receives id_token, which is basically a String Text. (That was made by the google api in the client-side, the javascript)
So, I do not have these parameter values passed to the server from the client. I know that google shows another way: the tokeninfo endpoint, but they mentioned that it is for only 100user/month only. (If I translated it correctly) However, for the tokeninfo endpoint way, they return the JSON file of containing client ids, which I think that would be the values for the parameters that I mentioned before, but I do not want to use the token info endpoint method.
So, my question is, how do I get the right parameter values for the sample code that is showed in the google document page? I only receive id_token value from the client.
ANDROID_CLIENT_ID or IOS_CLIENT_ID should be hard coded (in a config file) in your server's code.
Essentially your server is getting an id_token in a request and you need to make sure if it is meant for your app or server by checking the audience in there and making sure it matches one of the values you are expecting.

slack api rtm.start missing_scope needed client

I have get access token and when I try to post rtm.start, I am getting below error:
{
error = "missing_scope";
needed = client;
ok = 0;
provided = "identify,read,post";
}
I have set the scope to read,post,identify in authorize API. I have read the API document over and over again. Only rtm.start mentioned client scope. But in oauth document I didn't find a client scope. So, what's wrong?
You have to do it before you get the token.
when you do the initial request to connect the app, include &scope="identify,read,post,client"
Under App Credentials get your Client ID and Client Secret.
Goto:
https://#{team}.slack.com/oauth/authorize?client_id=#{cid}&scope=client
replacing #{team} and #{cid} with your values.
When you approve the authorization you’ll goto that real url that doesn’t resolve. Copy the whole url to your clipboard and paste it into a text file. Extract out just the “code” part.
Now goto:
https://#{team}.slack.com/api/oauth.access?client_id=#{cid}&client_secret=#{cs}&code=#{code}"
And you’ll get back a token like:
xoxp-4422442222–3111111111–11111111118–11aeea211e
(from here: https://medium.com/#andrewarrow/how-to-get-slack-api-tokens-with-client-scope-e311856ebe9)

Best way to upload files to Box.com programmatically

I've read the whole Box.com developers api guide and spent hours on the web researching this particular question but I can't seem to find a definitive answer and I don't want to start creating a solution if I'm going down the wrong path. We have a production environment where as once we are finished working with files our production software system zips them up and saves them into a local server directory for archival purposes. This local path cannot be changed. My question is how can I programmatically upload these files to our Box.com account so we can archive these on the cloud? Everything I've read regarding this involves using OAuth2 to gain access to our account which I understand but it also requires the user to login. Since this is an internal process that is NOT exposed to outside users I want to be able to automate this otherwise it would not be feasable for us. I have no issues creating the programs to trigger everytime a new files gets saved all I need is to streamline the Box.com access.
I just went through the exact same set of questions and found out that currently you CANNOT bypass the OAuth process. However, their refresh token is now valid for 60 days which should make any custom setup a bit more sturdy. I still think, though, that having to use OAuth for an Enterprise setup is a very brittle implementation -- for the exact reason you stated: it's not feasible for some middleware application to have to rely on an OAuth authentication process.
My Solution:
Here's what I came up with. The following are the same steps as outlined in various box API docs and videos:
use this URL https://www.box.com/api/oauth2/authorize?response_type=code&client_id=[YOUR_CLIENT_ID]&state=[box-generated_state_security_token]
(go to https://developers.box.com/oauth/ to find the original one)
paste that URL into the browser and GO
authenticate and grant access
grab the resulting URL: http://0.0.0.0/?state=[box-generated_state_security_token]&code=[SOME_CODE]
and note the "code=" value.
open POSTMAN or Fiddler (or some other HTTP sniffer) and enter the following:
URL: https://www.box.com/api/oauth2/token
create URL encoded post data:
grant_type=authorization_code
client_id=[YOUR CLIENT ID]
client_secret=[YOUR CLIENT SECRET]
code= < enter the code from step 4 >
send the request and retrieve the resulting JSON data:
{
"access_token": "[YOUR SHINY NEW ACCESS TOKEN]",
"expires_in": 4255,
"restricted_to": [],
"refresh_token": "[YOUR HELPFUL REFRESH TOKEN]",
"token_type": "bearer"
}
In my application I save both auth token and refresh token in a format where I can easily go and replace them if something goes awry down the road. Then, I check my authentication each time I call into the API. If I get an authorization exception back I refresh my token programmatically, which you can do! Using the BoxApi.V2 .NET SDK this happens like so:
var authenticator = new TokenProvider(_clientId, _clientSecret);
// calling the 'RefreshAccessToken' method in the SDK
var newAuthToken = authenticator.RefreshAccessToken([YOUR EXISTING REFRESH TOKEN]);
// write the new token back to my data store.
Save(newAuthToken);
Hope this helped!
If I understand correctly you want the entire process to be automated so it would not require a user login (i.e run a script and the file is uploaded).
Well, it is possible. I am a rookie developer so excuse me if I'm not using the correct terms.
Anyway, this can be accomplished by using cURL.
First you need to define some variables, your user credentials (username and password), your client id and client secret given by Box (found in your app), your redirect URI and state (used for extra safety if I understand correctly).
The oAuth2.0 is a 4 step authentication process and you're going to need to go through each step individually.
The first step would be setting a curl instance:
curl_setopt_array($curl, array(
CURLOPT_URL => "https://app.box.com/api/oauth2/authorize",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "content-type: application/x-www-form-urlencoded",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS =>
"response_type=code&client_id=".$CLIENT_ID."&state=".$STATE,
));
This will return an html text with a request token, you will need it for the next step so I would save the entire output to a variable and grep the tag with the request token (the tag has a "name" = "request_token" and a "value" which is the actual token).
Next step you will need to send another curl request to the same url, this time the post fields should include the request token, user name and password as follows:
CURLOPT_POSTFIELDS => "response_type=code&client_id=".$CLIENT_ID."&state=".$STATE."&request_token=".$REQ_TOKEN."&login=".$USER_LOGIN."&password=".$PASSWORD
At this point you should also set a cookie file:
CURLOPT_COOKIEFILE => $COOKIE, (where $COOKIE is the path to the cookie file)
This will return another html text output, use the same method to grep the token which has the name "ic".
For the next step you're going to need to send a post request to the same url. It should include the postfields:
response_type=code&client_id=".$CLIENT_ID."&state=".$STATE."&redirect_uri=".$REDIRECT_URI."&doconsent=doconsent&scope=root_readwrite&ic=".$IC
Be sure to set the curl request to use the cookie file you set earlier like this:
CURLOPT_COOKIEFILE => $COOKIE,
and include the header in the request:
CURLOPT_HEADER => true,
At step (if done by browser) you will be redirected to a URL which looks as described above:
http://0.0.0.0(*redirect uri*)/?state=[box-generated_state_security_token]&code=[SOME_CODE] and note the "code=" value.
Grab the value of "code".
Final step!
send a new cur request to https//app.box.com/api/oauth2/token
This should include fields:
CURLOPT_POSTFIELDS => "grant_type=authorization_code&code=".$CODE."&client_id=".$CLIENT_ID."&client_secret=".$CLIENT_SECRET,
This will return a string containing "access token", "Expiration" and "Refresh token".
These are the tokens needed for the upload.
read about the use of them here:
https://box-content.readme.io/reference#upload-a-file
Hope this is somewhat helpful.
P.S,
I separated the https on purpuse (Stackoverflow wont let me post an answer with more than 1 url :D)
this is for PHP cURL. It is also possible to do the same using Bash cURL.
For anyone looking into this recently, the best way to do this is to create a Limited Access App in Box.
This will let you create an access token which you can use for server to server communication. It's simple to then upload a file (example in NodeJS):
import box from "box-node-sdk";
import fs from "fs";
(async function (){
const client = box.getBasicClient(YOUR_ACCESS_TOKEN);
await client.files.uploadFile(BOX_FOLDER_ID, FILE_NAME, fs.createReadStream(LOCAL_FILE_PATH));
})();
Have you thought about creating a box 'integration' user for this particular purpose. It seems like uploads have to be made with a Box account. It sounds like you are trying to do an anonymous upload. I think box, like most services, including stackoverflow don't want anonymous uploads.
You could create a system user. Go do the Oauth2 dance and store just the refresh token somewhere safe. Then as the first step of your script waking up go use the refresh token and store the new refresh token. Then upload all your files.

Resources