Skip to content
This repository has been archived by the owner on Dec 10, 2024. It is now read-only.

Can i get private-token with my password? #267

Closed
LiangXugang opened this issue Dec 8, 2017 · 29 comments
Closed

Can i get private-token with my password? #267

LiangXugang opened this issue Dec 8, 2017 · 29 comments
Labels

Comments

@LiangXugang
Copy link

LiangXugang commented Dec 8, 2017

I can get my username&password only, and how can i create the go-gitlab client??
In my app, i cannot use OAUTH2.0 token anymore.

@svanharmelen
Copy link
Member

@LiangXugang you should be able to use your a private or personal token without any issues: https://docs.gitlab.com/ce/api/#personal-access-tokens

@clns
Copy link
Contributor

clns commented Dec 8, 2017

I think his requirement is to use the password to retrieve the token, as the token is not available at the time. This was achieved before GitLab 10.2.0 (2017-11-22) using the Session API which was recently removed.

At the moment I don't think this is possible.

@LiangXugang
Copy link
Author

@clns yep, i use the Session API in v3-Gitlab. But it's removed in v4-Gitlab.

As my user is just a general user(not admin), I cannot create a impersonation token too.

@clns
Copy link
Contributor

clns commented Dec 10, 2017

It’s removed in v3 as well, I think it’s removed in all api versions.

@svanharmelen
Copy link
Member

svanharmelen commented Dec 11, 2017

I'm really sorry, but I fail to understand what the actual problem is here. If I login to gitlab.comand go here:

image

I can create personal access tokens that I can use as private token in the go-gitlab package. Isn't that all you need? Or am I missing something here?

EDIT: I also noticed this in the docs:

When signing in to the main GitLab application, a _gitlab_session cookie is set. The API
will use this cookie for authentication if it is present, but using the API to generate a
new session cookie is currently not supported.

The primary user of this authentication method is the web frontend of GitLab itself,
which can use the API as the authenticated user to get a list of their projects, for
example, without needing to explicitly pass an access token.

So it seems that using a session cookie is no longer supported and is also no longer intended to be used by client applications.

@vitalyisaev2
Copy link

@svanharmelen there are a lot of use-cases when it's not possible to obtain personal access token via web UI. But Gitlab provides no other way to achieve this.

Suppose you're writing a service which interacts with Gitlab API and you have to write integration tests for it. On your CI system you deploy clean environment, run containers with Gitlab and your service. Than you need to get personal access token in order to provide access to Gitlab API for your service. How would you achieve this on a virtual machine without X server? In my opinion, possible options are:

  1. Hack Gitlab database and INSERT several rows about personal access tokens inside it;
  2. Deploy something like Selenium and imitate real user behavior in order to get token;
  3. Write a function that performs several GET and POST HTTP requests and parses resulting HTML to get token.

PR #269 adopts the third approach. I agree that it doesn't fit good to the purpose of this library (to support only API specification), but, as we can see, it can solve problems of many users of this library.

@svanharmelen
Copy link
Member

@vitalyisaev2 ok, I understand your use case. I do think it's kind of a corner case and I wonder if we should solve this in the go-gitlab package.

But on the other hand I think I can probably create a solution which would allow you to use this package with either a token or your username/password. So let me have a look at that instead, as I believe that would be the real solution right?

@svanharmelen
Copy link
Member

Hmm... One more question to understand your use case. If you spin up a new Gitlab setup in a container, then how do you get your username/password?

@vitalyisaev2
Copy link

@svanharmelen yes, I think, we are actually looking for a way to access API only with login / password.

@vitalyisaev2
Copy link

vitalyisaev2 commented Dec 11, 2017

Hmm... One more question to understand your use case. If you spin up a new Gitlab setup in a container, then how do you get your username/password?

@svanharmelen It's possible to set up root password via Docker environment variables (https://github.com/sameersbn/docker-gitlab)

@svanharmelen
Copy link
Member

Ok... So I've spent a reasonable amount of time on this today, only to come to the conclusion that it is not possible (given the current options the Gitlab API offers us) to create a decent way to support your use case.

I suggest you open an issue with Gitlab and explain them your use case to see if they can come up with either a solution or an API change (if they're willing to go that far) to support your use case.

Another solution could be that you add some logic to your CI setup so that it makes these additional calls to create and retrieve a personal token after spinning the Gitlab container, which it can than pass on the your application when starting it next.

If all else fails, I guess the only way forward is to add the function you showed in PR #269 in your own application, as I just don't feel comfortable with adding such a hacky function to this API client package.

@vitalyisaev2
Copy link

@svanharmelen thanks for your effort in investigation of this issue :)

There's a thread on Gitlab forum where several users are wondering how to work around it. I'll try to issue a feature request to Gitlab, but I'm sure they have reasons not to implement this feature, otherwise it should have been implemented a long time ago.

@vitalyisaev2
Copy link

vitalyisaev2 commented Dec 11, 2017

@svanharmelen
Copy link
Member

Hope anything useful comes out of it 👍

@michaellihs
Copy link
Contributor

michaellihs commented Dec 23, 2017

Maybe this SO post can be used as a foundation for how to get the access token / session token via curl requests https://stackoverflow.com/a/47952043/1549950

The session token should be usable for authenticating API requests as well - see https://docs.gitlab.com/ce/api/README.html#session-cookie

@svanharmelen
Copy link
Member

@michaellihs thanks for the reference. This is more of less what I tried last time I worked on this (see the last commit in this branch: https://github.com/xanzy/go-gitlab/tree/f-session), but I didn't quite get it working.

But now looking at it again, I think I see what I did wrong. So let me try to update my last attempt just a bit...

@svanharmelen
Copy link
Member

Yeah, so I didn't do anything wrong... The problem is that the CSRF token is only set in the metadata and not in a header. So in order to retrieve that value we would need to parse/scrape the returned webpage. And I really don't want to go that route. To me that just feels too bad/ugly 😞

@svanharmelen
Copy link
Member

Ok... This is still ugly, but it does work... So maybe we should merge this to offer users a solution to login using username password (see the last commit in the f-session branch).

@svanharmelen
Copy link
Member

@LiangXugang @michaellihs @vitalyisaev2 could you guys please give this a good test run to make sure it works as expected? Both the new NewBasicAuth client as the old existing clients (to make sure they also still work as expected).

My own quick tests seem to indicate it should be good now...

@vitalyisaev2
Copy link

@svanharmelen thank you, I haven't seen f-session branch yet, I'll give it a try in a couple of days...

@michaellihs
Copy link
Contributor

Oh oh - my comment was not intended to trigger so much work... I had a different problem regarding the question how to get a plain Gitlab Docker image configured in such a way that I can use it for end2end testing in Travis CI - therefore the hacky bash solution was fine... but it's great that you provide a way to do this through go-gitlab now, this offers me the possibility to login to Gitlab with username and password from the command line. I will give it a try the next couple of days. Thanks a lot for your effort!

@svanharmelen
Copy link
Member

No worries... I’ve looked at this a little while ago (when this issue was opened), but stopped when I found it to become too ugly.

So your comment only triggered me to look at that commit again and think about the options.

So it wasn’t that much work to be honest, and I guess it adds some value. So all is good 👍

@michaellihs
Copy link
Contributor

Hm - regarding the title of the issue, I have to admit that I was expecting a little "extra functionality" in the f-session branch: what is implemented now is, that I can login the client with username and password. But what I think is still missing, is that I can generate an access token with the library. So I don't know whether the second part was left out intentionally or just isn't implemented yet.

There seems to be another implementation now, that follows the approach I did with curl using golang: https://github.com/solidnerd/gogpat - maybe we can add this to go-gitlab?

@svanharmelen
Copy link
Member

I was thinking about adding this API endpoint to the package: https://docs.gitlab.com/ce/api/users.html#create-an-impersonation-token

When being able to use the package with username and password, then you can create a token using a stabdard API call.

Together that should solve this issue, right?

@michaellihs
Copy link
Contributor

michaellihs commented Dec 26, 2017

Impersonation token endpoints are already implemented, I think: https://github.com/xanzy/go-gitlab/blob/master/users.go#L596 ff - but I didn't know that one can use them as a replacement for the auth tokens... so it's all there, I guess :)

@michaellihs
Copy link
Contributor

michaellihs commented Dec 26, 2017

@svanharmelen I tested your new basic auth client via

func getImpersonationToken(host string, username string, password string) (string, error) {
	baseUrl, err := url.Parse(host)
	if err != nil {
		return "", errors.New(fmt.Sprintf("could not parse given host '%s': %s", baseUrl, err))
	}

	client, err := initHttpClient()
	if err != nil {
		return "", err
	}

	loginClient, err := gitlab.NewBasicAuthClient(client, host, username, password)

	user, _, err := gitlabClient.Users.CurrentUser()
	if err != nil {
		return "", err
	}

	tokenName := "golab"
	scopes := []string{"api"}
	opts := &gitlab.CreateImpersonationTokenOptions{
		Name:   &tokenName,
		Scopes: &scopes,
	}

	token, _, err := loginClient.Users.CreateImpersonationToken(user.ID, opts)
	if err != nil {
		return "", err
	}
	return token.Token, nil
}

The first (GET) request user, _, err := gitlabClient.Users.CurrentUser() is working as expected (which means that the user is successfully logged in), but the second request gave me a strange error in the gitlab-rails/production.log:

Started POST "/api/v4/users/1/impersonation_tokens" for 172.17.0.1 at 2017-12-26 22:44:15 +0000
Processing by Gitlab::RequestForgeryProtection::Controller#index as JSON
  Parameters: {"name"=>"golab-2017-12-26T23:44:10.377768+01:00", "scopes"=>["api"], "request_forgery_protection"=>{"name"=>"golab-2017-12-26T23:44:10.377768+01:00", "scopes"=>["api"]}}
Can't verify CSRF token authenticity
This CSRF token verification failure is handled internally by `GitLab::RequestForgeryProtection`
Unlike the logs may suggest, this does not result in an actual 422 response to the user
For API requests, the only effect is that `current_user` will be `nil` for the duration of the request
Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms)

My assumption was, that every API request that is session-authenticated needs to pass a CSRF check, since GitLab assumes, that such a request comes from the frontend (UI), hence has a valid CSRF token. That is also the reason, why the GET request works whereas the POST/PUT doesn't.

Dirty fix: get yourself ANY CSRF token before you send a POST/PUT request and add it to the request:

func getImpersonationToken(host string, username string, password string) (string, error) {

	// ... like before

	csrfToken, err := getCsrfToken(host+"/profile", client)
	setCsrfToken := func(req *http.Request) error {
		req.Header.Set("X-CSRF-Token", csrfToken)
		return nil
	}

	token, _, err := loginClient.Users.CreateImpersonationToken(user.ID, opts, setCsrfToken)
	if err != nil {
		return "", err
	}
	return token.Token, nil
}

func getCsrfToken(url string, client *http.Client) (string, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	match := regexp.MustCompile(`"csrf-token" content="(.*?)"`).FindSubmatch(body)
	if len(match) != 2 {
		return "", fmt.Errorf("unable to retieve CSRF token")
	}
	return string(match[1]), nil
}

Since this solution is quite flaky, I assume that you should not merge the current implementation yet. At least the CSRF generation / addition to header should be added to the client or much facilitated by the library...

Related:

@svanharmelen
Copy link
Member

Oh, that’s a shame (that all session based API calls require an updated CSRF token)... I was under the impression it was only needed when POSTing to website endpoints (so everything not starting with https://gitlab.com/api/v4).

In this case I again very much doubt if we should this at all. It’s just plain hacking around the API, instead of using the API. And this is suppose to be an API client...

So going to sleep over it, but think we should close this as a won’t fix until the API offers a way to get around this.

@svanharmelen
Copy link
Member

Thought about it, but unless someone else comes up with a proper solution I don't think this code should be added to go-gitlab. So closing this one for now...

@svanharmelen
Copy link
Member

@LiangXugang this should now be possible using the NewBasicAuthClient!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

5 participants