Accessing GitLab via API with Ruby - ruby

Trying to access my university's GitLab for the first time via API (I'm the repo owner but have no access to the console and can not edit config files or restart the server), I seem to lack some mandatory basics. Tried to use Ruby (no rails) with the Narkoz API wrapper. Unfortunately, there are no tutorials available which explain the very first step:
How to connect to the server and authenticate using UID+passwd?
Can anybody explain the process in human-readable instructions? The GitLab Manual was not helpful for me.
I hope that I could then figure out how to add GitLab users to my repository. Don't want to add 100 users using the web interface.

The manual entry you linked is for signing into GitLab with a third-party OAuth provider, which doesn't sound like what you're trying to do. What it sounds like you're trying to do is request an OAuth token which you can then use to access GitLab's API.
From the documentation:
In this flow, a token is requested in exchange for the resource owner credentials (username and password). The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the client is part of the device operating system or a highly privileged application), and when other authorization grant types are not available (such as an authorization code).
Which sounds like what you're trying to do.
One important thing of note from the documentation:
Deprecation notice: Starting in GitLab 8.11, the Resource Owner Password Credentials has been disabled for users with two-factor authentication turned on. These users can access the API using personal access tokens instead.
If this is the case for you, the following won't work and you'll need to generate an access token instead.
1. Requesting access token
POST request to /oauth/token with parameters:
{
"grant_type" : "password",
"username" : "user#example.com",
"password" : "secret"
}
Then, you'll receive the access token back in the response:
{
"access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
"token_type": "bearer",
"expires_in": 7200
}
You would then assign this token as your GitLab.private_token.

For the records -- what would have helped quicker is a minimum viable example -- especially since the API documentation contains known and not fixed errors.
host = myGitlabServer
require 'oauth2'
client = OAuth2::Client.new(clientIdFromGitLab, clientSecretFromGitLab, :site => host)
accessToken = client.password.get_token(myGitLabUid, myGitLabPassword)
require 'httparty'
# get own user data
response = HTTParty.get(host + '/api/v4/user?access_token=' + accessToken.token)
puts response.parsed_response
# get user data by user name
response = HTTParty.get(host + '/api/v4/users?username=' + username + '&access_token=' + access_token.token)
puts response.parsed_response[0]
# add user as reporter to project
response = HTTParty.post(host + '/api/v4/projects/' + projectId + '/members/?user_id=' + newUID + '&access_level=20&access_token=' + access_token.token)
puts response.parsed_response

or, you can use the excellent gitlab gem.
https://github.com/narkoz/gitlab

Related

Client secret + refreshing the access token in spring oauth2

I am using spring boot for backend and Android device for frontend of my system.
Right now I am facing the challenge to use Spring-OAuth2 to secure my resource server.
I have some questions, which I want to discuss with you:
My knowledge + this tutorial are saying that I should use the OAuth2.0 "password" grant type for my mobile app to obtain an access token. The official spring tutorial for security gives an example how to obtain the access token using password grant type:
$ curl client:secret#localhost:8080/oauth/token -d grant_type=password -d username=user -d password=pwd
And here comes my first question: Is there any possibility to obtain access token using the password grant type without sending the "client secret" ?
Since the client secret could be "reverse engineered" by decompiling the client app. The obtaining access token without secret should be somehow possible, because Facebook SDK for Android also does not need the client_secret in the mobile app.
I think here I have a little trouble understanding why the clientID + clientSecret needs to be included in the request above, because, since there are already username + password included, it should be possible to generate the access token, so does this brings a next level of security ? and does it implies the following (example): I am logged in as Filip in my Android client and I am sending the access token A with each request to the server. Then I log in as Filip into web client and I try to access the resource server from web client using the access token A, which is not possible because access token A was issued only for Android client ?
The next question is how can I refresh the obtained access token ?
I was trying to do so using the command below, but I got "Full authentication is required to access this resource." After I got the new refreshed token, can I use the refresh token to refresh my new access token again ?
curl -v --data "grant_type=refresh_token&client_id=acme&client_secret=acmesecret&refresh_token=REFRESH_TOKEN" http://localhost:9999/uaa/oauth/token
Thank you
The OAuth 2.0 spec allows for so-called public clients i.e. clients that don't authenticate themselves. So it is possible to use the Resource Owner Password Credentials grant with a public client, i.e. one that does not need to send a client secret. It does mean that the Authorization Server cannot assume anything about the client since a client_id is not a secret and there's no way to prevent a malicious client using this grant type or clients from impersonating each other. So using it in this way comes at the cost of reduced security although one may argue that in your case there's no way to use confidential clients anyhow, so there's no difference.
In general the Resource Owner Password Credentials grant is an anti-pattern for OAuth and only meant for migration purposes because it defeats most of the goals of OAuth in itself.
Access tokens are issued on a per-client basis.
You refresh token request seems OK but the Authorization Server may require basic authentication instead of providing the client_id/client_secret as post parameters, considering that you did the same for the original access token request.

How can I pre-authorize a client app for my user on my oauth provider that uses doorkeeper?

I've written an oauth provider that is meant to work with several of my company's web applications. I am using the doorkeeper gem, which has worked well so far.
Typical behavior is for a user to go to the client application, get redirected to the provider to sign in, confirm that the client application is authorized to access that user's information, and get redirected back to the client application. However, I'd like to skip the step of the user confirming the client application. I'd like to do it for them, so there is no prompt.
I tried to mimic code I found here with something like:
Doorkeeper::Application.all.each do |application|
auth_params = {response_type: 'code', client_id: application.uid, redirect_uri: application.redirect_uri}
client = Doorkeeper::OAuth::Client.find(application.uid)
authorization = Doorkeeper::OAuth::AuthorizationRequest.new(client, user, auth_params)
authorization.authorize
end
but that didn't work, it still gives the user the Authorize/Deny prompt for a client app. Suggestions?
OAuth has the Resource Owner Credentials Grant flow for this, which Doorkeeper supports. Basically you request an access token with the user credentials (username and password). This way you skip the user confirmation and you also don't need a callback URL.
To configure Doorkeeper:
Doorkeeper.configure do
resource_owner_from_credentials do |routes|
User.authenticate!(params[:username], params[:password]) # change this if needed
end
end
Example token request:
curl -i https://example.com/oauth/token \
-F grant_type=password \
-F client_id=<client_id> \
-F client_secret=<client_secret> \
-F username=user#example.com \
-F password=password
If your OAuth client applications are Rails applications you can use the oauth2 gem for this:
client = OAuth2::Client.new('client_id', 'client_secret', :site => "https://example.com")
access_token = client.password.get_token('user#example.com', 'password')
Also see Doorkeepers wiki:
https://github.com/applicake/doorkeeper/wiki/Using-Resource-Owner-Password-Credentials-flow
Doorkeeper 0.6.7 provides configure option to do this.
To configure Doorkeeper:
Doorkeeper.configure do
skip_authorization do
true
end
end
You can have your app preauthorize either all client apps by adding
skip_authorization do
true
end
to doorkeeper initializer, or on a per app basis by adding a boolean preauthorized to the Doorkeeper oauth_applications table. Then add something like this to the initializer:
skip_authorization do |resource_owner, client|
client.application.preauthorized?
end
You can acquire the token for your application to bypass that confirmation screen a post to /oauth/token. Tweak it to your liking.
In your client application:
require 'rest-client'
require 'json'
client_id = '4ea1b...'
client_secret = 'a2982...'
response = RestClient.post 'http://localhost:3000/oauth/token', {
grant_type: 'client_credentials',
client_id: client_id,
client_secret: client_secret
}
token = JSON.parse(response)["access_token"]
Now you can request access to protected resources that does not require a resource owner:
RestClient.get 'http://localhost:3000/api/v1/profiles.json', { 'Authorization' => "Bearer #{token}" }
Source: https://github.com/applicake/doorkeeper/wiki/Client-Credentials-flow
From your question, it seems, your company has many application and you wish to use one authentication platform for all of these.
Now, I would presume you would want to have the login screen at one place (presumably at the authenticator app). If that's the case, you will NOT be able to use Resource Owner Credentials Grant flow for this.
The best way would be have a list of trustworthy clients and skip authorisation conditionally as follows:
# config/initializers/doorkeeper.rb
Doorkeeper.configure do
skip_authorization do |resource_owner, client|
client.uid == "client application id of the trusted app goes here"
end
end
If you want to have the clients have their own login screens, the Resource Owner Credentials Grant flow would suffice.

How to deploy multiple Rack/Sinatra apps with Passenger (on Apache) with HTTP Basic Authentication?

The issue here is with multiple instances of the same Sinatra (Rack) app deployed on Passenger+Apache on different sub URIs with HTTP basic auth to keep away unwanted access:
I have 4 instances of a Sinatra app deployed on my domain as:
example.com/private/foo
example.com/private/moo
...
...
Access to all of them is protected by HTTP basic authentication using the Rack::Auth::Basic middleware. config.ru for all of them look like:
# ...
users = {'user' => 'password'}
use Rack::Auth::Basic, 'realm' do |username, password|
users.key?(username) && users[username] == password
end
run MyApp
The only thing the changes from one config.ru to another is the 'realm' parameter.
Now the issue is that once I have logged into one of the apps, say private/foo, Chrome doesn't prompt me for a username and password for other apps (private/moo etc.). This is counterintuitive since all instances are uniquiely identified by their URLs. Using different credentials for each instance does work, but shouldn't Chrome request credentials at least once for each instance? One thing I noticed is that the first time I log into one of the instances, Chrome says 'The server at example.com:80 requires a username and password'. I would have expected 'The resource example.com/private/foo requires a username and password'. Isn't that how it is supposed to work?
I checked Rack::Auth::Basic source code and Wikipedia's article on HTTP Basic Auth and came up with nothing to help my case :(.
In basic authentication, the realm parameter isn't send back to the server. So the server can't really check if the client is sending authorization header for the same realm or not. It depends on the client. Rack's implementation of HTTP basic authentication is correct. So:
Now the issue is that once I have logged into one of the apps, say private/foo, Chrome doesn't prompt me for a username and password for other apps (private/moo etc.). This is counterintuitive since all instances are uniquiely identified by their URLs.
As Andrew pointed out and is clear from the RFC, URL doesn't play a role there. But if '/foo' is protected, '/foo/moo' is protected under the same realm.
Using different credentials for each instance does work, but shouldn't Chrome request credentials at least once for each instance?
Under the scenes what is happening (on inspecting with debugger tools) is that, after I have logged once into one of the apps, say private/foo, Chrome re-sends the same authorization header to other apps, say private/moo, without being challenged first.
The RFC says that the client may send the corresponding authorization header for a realm without being challenged by the server first.
Looks like Chrome is either treating all my apps to be under the same realm or re-sending the same authorization header across different realms. I don't think that is the expected behavior but I could be missing something. Firefox behaves same. Anyway, that wasn't the essence of the question.
The theme of the question was "How do I get Chrome to request me username and password at least once for each instance? Basic auth isn't working the way I expected it to; why?"
Use Digest authentication (RFC 2617 again). Rack implements the MD5 algorithm version under Rack::Auth::Digest::MD5. Set different opaque for each instance and you are good to go:
# ...
realm = "Description of the protected area."
opaque = "Secret key that uniquely identifies a realm."
users = {'user' => 'password'}
use Rack::Auth::Digest::MD5, realm, opaque do |username|
users[username]
end
opaque is sent back by the client and can be verified on the server side that the authorization request is for the correct resource. Job of realm seems descriptory in nature -- which area or resource are you trying to protect? what id do I flash?
RFC: https://www.rfc-editor.org/rfc/rfc2617

What's the simplest sample program for twitter4r which is likely to work?

I'm trying the following sample code, and failing (the uid and password I'm using are valid). Is there something I'm missing, or a simpler example I can try?
testing.rb:
require('rubygems')
gem('twitter4r','>=0.2.0')
require('twitter')
client = Twitter::Client.new(:login => 'uid', :password => 'password')
ARGV.each do |a|
#message = "#{a}"
end
status = client.status(:post, #message)
prompt> ruby testing.rb "test"
/Library/Ruby/Gems/1.8/gems/twitter4r-0.6.0/lib/twitter/client/base.rb:120:in
`raise_rest_error': Unauthorized
(Twitter::UnauthorizedError) from
/Library/Ruby/Gems/1.8/gems/twitter4r-0.6.0/lib/twitter/client/base.rb:125:in
`handle_rest_response' from
/Library/Ruby/Gems/1.8/gems/twitter4r-0.6.0/lib/twitter/client/base.rb:23:in `rest_oauth_connect' from
/Library/Ruby/Gems/1.8/gems/twitter4r-0.6.0/lib/twitter/client/status.rb:42:in `status' from testing.rb:11
#blueberryfields you will need to use the OAuth API that Twitter4R v0.5.0+ supports. This is due to Twitter.com mandating OAuth authentication as of August 2010. Supplying the login and password of your username is no longer supported either via Twitter4R, twitter.com or any other Twitter API client.
There is a fantastic tutorial on using OAuth with Twitter4R at this blog:
http://blog.monnet-usa.com/?p=342
HTH,
#SusanPotter -- Author of Twitter4R
PS Also check out #t4ruby for updates to Twitter4R
Twitter doesn't allow basic Auth (username+password) logins through their API anymore.
You should look for a method that supports OAuth-based login.
You'll need to fetch OAuth keys for your application, which can be done from the following links. The first link allows you to enroll a new application, the second one allows you to see what applications you've registered.
New Twitter Application # dev.twitter.com
Twitter Applications (Existing) # dev.twitter.com
A more in-depth guide is available at the following link. You will want to read this as OAuth requires at least two steps to authenticate before you can use the twitter API.
Authenticating Requests with OAuth

Ruby twitter client

I'm trying to create a ruby-based twitter client where I can post my status from command-line using ruby. I'm trying to understand the oauth right now, and it confused me a little bit. If I'm building a web application, I can provide a callback url when the request token is complete. How would I do that from the command-line? I don't want ruby to print out the authorized and copy and paste the url and click 'Allow' to get the token. I found something about out-of-band exchange or PIN. How would I do that with OAuth library in ruby, please thank you very much.
To use the callback url mechanism, you application should be a web application. It seems you are developing desktop application and if that's the case, you should follow "PIN code" flow by supplying an oauth_callback_url of "oob" (out-of-band) when you request token. Like this,
https://api.twitter.com/oauth/request_token?oauth_callback=oob
If you properly set a header of this HTTP request (setting HTTP header is the key part of OAuth and I think you already know how to do this), Twitter will give oauth_token, oauth_token_secret and oauth_verifier. Let's call this token "request_token". You need it to get "access_token" later.
Once you have request_token, you need to open web page with the below url
http://api.twitter.com/oauth/authorize?oauth_token=request_token
This will open the authorization page and let a user to decide whether the user wants to allow your application to access his or her Twitter account. If the use says okay, then Twitter gives PIN code. You need to allow a user to type the PIN code so that you can save it.
Now, it's time to get another token ("access_token") by using your comsumer_key / secret, request_token and the PIN code. You should set header with all these values correctly and do HTTP request again with this url,
https://api.twitter.com/oauth/access_token
If Twitter accepts your "access_token" request, it will give you oauth_token, oauth_token_secret, user_id and screen_name. Let's call this token "access_token". Now, you can perform any OAuth required Twitter API by using access_token and its secret (oauth_token_secret). You can save the two values in a file and keep using them whenever you need to access the user's Twitter account. The values will be always valid until the user revokes the access to your application.
I don't know Ruby but if you know how to perform HTTP/HTTPS requests (GET / POST) with custom headers in Ruby, this PIN code flow should work fine if you follow Twitter API document carefully. Good Luck!
It will ask for the PIN code until you specify the oauth_callback when getting the request token, not when forwarding the user to the authorization url
#consumer = OAuth::Consumer.new(
TWITTER_CONSUMER_KEY,
TWITTER_CONSUMER_SECRET,
{:site=>"https://api.twitter.com"})
#request_token = #consumer.get_request_token( :oauth_callback => CALLBACK_URL )
This was the result of surfing several hours of incomplete documentation.
use Twitter gem, it will make things easier for you. http://rdoc.info/gems/tweeter/2.0.0/frames

Resources