I need to download a text file using basic authentication (the kind of authentication that prompts the browser to ask you for domain\username and password) using Groovy. I would like to avoid using additional libraries, isn't there anything to do this in in Groovy?
My current code is:
new File("test.txt").withOutputStream { out ->
def url = new URL(myurl).openConnection()
def remoteAuth = "Basic " + "myusername:mypassword".bytes.encodeBase64()
url.setRequestProperty("Authorization", remoteAuth);
out << url.inputStream
}
But the server replies with a 401 error. What should I do?
Groovy uses the java.net.Authenticator API. You can provide a default Authenticator using java.net.Authenticator#setDefault. An Example for an BasicAuth usage can be found in another Answer.
Authenticator.setDefault (new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication ("username", "password".toCharArray());
}
});
Related
I am exploring using HttpBuilder-NG to perform REST calls. My first attempt simply gives me a groovy.net.http.HttpException with a traceback. No other details. Using the previous HttpBuilder I was able to turn on logging to see what is sent and received.
The code is pretty simple:
def jiraUrl = 'https://gpdevjira.broadinstitute.org:8443/rest/api/2'
def username = 'releng'
def password = 'releng'
#Test
void testGetIssue() {
def jira = configure {
request.uri = jiraUrl
request.auth.basic username, password
}
assert jira
String text = jira.get(String){
request.uri.path = "/issue/$testIssue"
}
assert text
}
I think that the question has been answered with the suggestion to use the Apache client implementation of HttpBuilder-NG
I'm using Retrofit (with RxJava) to download a text file.
#Streaming
#GET
Observable<ResponseBody> download(#Url String file);
Everything works fine except when user is using a public wifi that need web authentication. In this case download is still successful and what I receive is a file contains HTML of that authentication web page. How do I check and raise error in this case? I tried with Interceptor, NetworkInterceptor (Okhttp), disable redirect (Retrofit) but no luck because no exception is thrown.
Either checksum your file and check it when you get it, or check the Content-type:
#Streaming
#GET
Observable<Response<ResponseBody>> download(#Url String file);
download("http://...")
.map(response -> {
if(!"application/my-content-type".equals(
response.headers("Content-Type")) {
throw new RuntimeException("Bad download");
}
return resource.body();
})
I'm trying to use the Mailchimp API version 3.0 with basic auth. I'm using Classic ASP.
The Json response is always: "API Key Missing".
Set HttpReq = Server.CreateObject("MSXML2.ServerXMLHTTP")
HttpReq.open "GET", "https://us4.api.mailchimp.com/3.0/", False
HttpReq.setRequestHeader "Content-Type", "application/json"
HttpReq.setRequestHeader "apikey", "xxxxxx"
HttpReq.send ""
Response.Write HttpReq.ResponseText
Set HttpReq = Nothing
I'm sending it as a header.
What am I doing wrong..?
The answer is:
HttpReq.setRequestHeader "Authorization", "apikey xxxxxx"
If you're trying to use Basic Auth, you need to actually follow the spec. You can build the header yourself using the wiki article as your guide, but the easiest thing is to just use your HTTP Library's built-in support for that. In your case, this will probably help.
To roll your own, you need two pieces of information. The first is the username the second is the password. For MailChimp v3.0, the username can be anything. I tend to use 'apikey' as the username. The password is the API Key itself. Let's say my API Key is 'xxxxxxxxxx-yyy'. Now, you Base 64 Encode the string apikey:xxxxxxxxxx-yyy. That gives me YXBpa2V5Onh4eHh4eHh4eHgteXl5. Now the header I create is:
Authorization: Basic YXBpa2V5Onh4eHh4eHh4eHgteXl5
The method you're using will work, but is very custom to MailChimp and might confuse future visitors to your code.
I see that the question was based on #C but I had the same problem with java.(I am new to java so fell free to edit my code).
public String get(String url) throws IOException {
HttpGet get = new HttpGet(url);
String apiEncode = "apikey:9590e52MyAPI8-us9";
String encoding = Base64.encodeBytes(apiEncode.getBytes());
get.setHeader("Authorization","Basic " + encoding );
HttpResponse response = http.execute(get);
if (response.getEntity() != null) {
return EntityUtils.toString(response.getEntity(), "UTF-8").trim();
} else {
throw new IOException(response.getStatusLine().toString());
}
}
I've gone through plenty of Google documentation and SO Q/A's but with no luck. I wonder if anyone has yet succesfully used the OpenId to OpenId Connect migration as advised by Google.
This is what we used to do:
IAuthenticationResponse response = _openid.GetResponse();
if (response != null) {
//omitted for brevity
} else {
IAuthenticationRequest req = _openid.CreateRequest("https://www.google.com/accounts/o8/id");
req.AddExtension(new ClaimsRequest
{
Country = DemandLevel.Request,
Email = DemandLevel.Request,
Gender = DemandLevel.Require,
PostalCode = DemandLevel.Require,
TimeZone = DemandLevel.Require
});
req.RedirectToProvider();
}
That was done using a version of DotNetOpenAuth that dates back a few years. Because Google has deprecated OpenId authentication we are trying to move over to OpenID Connect. The key question here is: can I somehow get my hands on the OpenId identifier (in the form of https://www.google.com/accounts/o8/id?id=xyz) using the latest version of DotNetOpenAuth library or by any other means?
I have tried the latest DotNetOpenAuth and I can get it to work but it gives me a new Id (this was expected). I have also tried the Javascript way by using this URL (line breaks for readibility):
https://accounts.google.com/o/oauth2/auth?
scope=openid%20profile%20email
&openid.realm=http://localhost/palkkac/
&client_id=//here is the client id I created in google developer console
&redirect_uri=http://localhost/palkkac/someaspxpagehere
&response_type=id_token%20token
I checked (using Fiddler) the realm value that we currently send using the old DotNetOpenAuth code and it is http://localhost/palkkac/. I've put the same realm in the url above. The redirect url starts with the realm value but it is not entirely the same.
When I redirect to a simple page that parses the id_token and decrypts it (using the https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=zyx endpoint) I get this:
audience "client id is here"
email "mikkark#gmail.com"
expires_in 3597
issued_at //some numbers here
issued_to "client id is here"
issuer "accounts.google.com"
user_id "here is a sequence of numbers, my id in the OpenID Connect format that is"
verified_email true
So there is no sign of the openid_id field that you would expect to find here, though the whole structure of the message seems different from the Google docs, there is no field titled sub, for example. I wonder if I'm actually using the wrong endpoint, parameters or something?
What I have been reading is the migration guide: https://developers.google.com/accounts/docs/OpenID. I skipped step 2 because it seemed like an optional step. In step 3 the field openid_id is discussed and I would like to get that to work as a proof-of-concept first.
We registered the app on Google in order to create the client id etc. There are now also numerous allowed redirect url's as well as javascript origins listed in the Google dev console. Let me know if those might mess up the system and I'll post them here for review.
Side note: we are supposed to be moving our app behind a strictly firewalled environment where we would need to open ports in order to do this on the server side. Therefore, a client-side Javascript solution to access Google combined with HTTPS and redirecting the result to the server would be prefered (unless there are other issues that speak against this).
There are other resources on SO regarding this same issue, although all of these seem to use different libraries on the server side to do the job and nobody seems to have made any attempts at using Javascript:
Here (https://stackoverflow.com/questions/22842475/migrating-google-openid-to-openid-connect-openid-id-does-not-match) I think the problem was resolved by setting the realm to be the same as in the old OpenId2.0 flow. This does not seem to work in my case.
over here the openid_id field is also missing, but the problem here is more about how to request the id_token from Google using libraries other than DotNetOpenAuth.
and in here there seem to be similar problems getting Google to return the openid_id field.
You can use the GoogleAuthentication owin middleware.
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
{
SignInAsAuthenticationType = signAs,
AuthenticationType = "Google",
ClientId = "xxx.apps.googleusercontent.com",
ClientSecret = "xx",
CallbackPath = PathString.FromUriComponent("/oauth2callback"),
Provider = new GoogleOAuth2AuthenticationProvider
{
OnApplyRedirect = context =>
{
context.Response.Redirect(context.RedirectUri + "&openid.realm=https://mydomain.com/"); // DotNetOpenAuth by default add a trailing slash, it must be exactly the same as before
}
},
BackchannelHttpHandler = new MyWebRequestHandler()
}
Then, add a new class called MyWebRequestHandler:
public class MyWebRequestHandler : WebRequestHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var httpResponse = await base.SendAsync(request, cancellationToken);
if (request.RequestUri == new Uri("https://www.googleapis.com/plus/v1/people/me")) return httpResponse;
var configuration = await OpenIdConnectConfigurationRetriever.GetAsync("https://accounts.google.com/.well-known/openid-configuration", cancellationToken); // read the configuration to get the signing tokens (todo should be cached or hard coded)
// google is unclear as the openid_id is not in the access_token but in the id_token
// as the middleware dot not expose the id_token we need to parse it again
var jwt = httpResponse.Content.ReadAsStringAsync().Result;
JObject response = JObject.Parse(jwt);
string idToken = response.Value<string>((object)"id_token");
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
try
{
SecurityToken token;
var claims = tokenHandler.ValidateToken(idToken, new TokenValidationParameters()
{
ValidAudience = "xxx.apps.googleusercontent.com",
ValidIssuer = "accounts.google.com",
IssuerSigningTokens = configuration.SigningTokens
}, out token);
var claim = claims.FindFirst("openid_id");
// claim.Value will contain the old openid identifier
if (claim != null) Debug.WriteLine(claim.Value);
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
return httpResponse;
}
}
If like me you found this not really straightforward, please help by upvoting this issue https://katanaproject.codeplex.com/workitem/359
I am having to re-write an existing REST API using .NET (originally written with Ruby). From the client's perspective, it has to work exactly the same way as the old API - i.e. the client code mustn't need to change. The current API requires Basic Authentication. So to call the old API, the following works perfectly:-
var wc = new System.Net.WebClient();
var myCache = new CredentialCache();
myCache.Add(new Uri(url), "Basic", new NetworkCredential("XXX", "XXX"));
wc.Credentials = myCache;
var returnBytes = wc.DownloadData("http://xxxx");
(I have had to ommit the real URL / username / password etc for security reasons).
Now I am writing the new API using ASP.Net Web API with MVC4. I have a weird problem and cannot find anybody else with exactly the same problem. In order to support Basic Authentication, I have followed the guidelines here:
http://sixgun.wordpress.com/2012/02/29/asp-net-web-api-basic-authentication/
One thing, I put the code to "hook in the handler" in the Global.asax.cs file in the Application_Start() event (that wasn't explained so I guessed).
Anyway, if I call my API (which I have deployed in IIS) using the above code, the Authorization header is always null, and the above fails with 401 Unauthorized. However, if I manually set the header using this code, it works fine - i.e. the Authorization header now exists and I am able to Authenticate the user.
private void SetBasicAuthHeader(WebClient request, String userName, String userPassword)
{
string authInfo = userName + ":" + userPassword;
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
request.Headers["Authorization"] = "Basic " + authInfo;
}
.......
var wc = new System.Net.WebClient();
SetBasicAuthHeader(request, "XXXX", "XXXX");
var returnBytes = wc.DownloadData("http://xxxx");
Although that works, it's no good to me because existing users of the existing API are not going to be manually setting the header.
Reading up on how Basic Authentication works, the initial request is meant to be anonymous, then the client is returned 401, then the client is meant to try again. However if I put a break point in my code, it will never hit the code again in Antony's example. I was expecting my breakpoint to be hit twice.
Any ideas how I can get this to work?
You're expecting the right behavior. System.Net.WebClient does not automatically include the Authorization headers upon initial request. It only sends them when properly challenged by a response, which to my knowledge is a 401 status code and a proper WWW-Authenticate header. See here and here for further info.
I'm assuming your basic authentication handler is not returning the WWW-Authenticate header and as such WebClient never even attempts to send the credentials on a second request. You should be able to watch this in Fiddler or a similar tool.
If your handler did something like this, you should witness the WebClient approach working:
//if is not authenticated or Authorization header is null
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
response.StatusCode = HttpStatusCode.Unauthorized;
response.Headers.Add("WWW-Authenticate", "Basic realm=\"www.whatever.com\"");
return response;
});
//else (is authenticated)
return base.SendAsync(request, cancellationToken);
As you noticed, if you include the Authorization headers on every request (like you did in your alternative approach) then your handler already works as is. So it may be sufficient - it just isn't for WebClient and other clients that operate in the same way.