2022-10-18 01:24:12 +00:00
// Copyright 2022 Woodpecker Authors
2018-02-19 22:24:10 +00:00
// Copyright 2018 Drone.IO Inc.
2018-03-21 13:02:17 +00:00
//
2018-02-19 22:24:10 +00:00
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
2018-03-21 13:02:17 +00:00
//
2018-02-19 22:24:10 +00:00
// http://www.apache.org/licenses/LICENSE-2.0
2018-03-21 13:02:17 +00:00
//
2018-02-19 22:24:10 +00:00
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2016-04-19 04:40:49 +00:00
package bitbucketserver
2016-05-01 23:30:00 +00:00
// WARNING! This is an work-in-progress patch and does not yet conform to the coding,
// quality or security standards expected of this project. Please use with caution.
2016-04-19 05:32:40 +00:00
2016-04-19 04:40:49 +00:00
import (
2021-09-28 10:56:59 +00:00
"context"
2016-05-01 23:30:00 +00:00
"crypto/rsa"
2016-06-26 05:27:09 +00:00
"crypto/tls"
2016-05-01 23:30:00 +00:00
"crypto/x509"
"encoding/pem"
2016-04-20 18:59:47 +00:00
"fmt"
2016-06-26 05:27:09 +00:00
"net/http"
2022-08-29 23:14:07 +00:00
"os"
2017-03-18 11:25:53 +00:00
2021-06-22 10:34:35 +00:00
"github.com/mrjones/oauth"
2021-10-12 07:25:13 +00:00
2022-11-04 23:35:06 +00:00
"github.com/woodpecker-ci/woodpecker/server/forge"
"github.com/woodpecker-ci/woodpecker/server/forge/bitbucketserver/internal"
"github.com/woodpecker-ci/woodpecker/server/forge/common"
2022-11-06 11:44:04 +00:00
forge_types "github.com/woodpecker-ci/woodpecker/server/forge/types"
2021-09-27 17:51:55 +00:00
"github.com/woodpecker-ci/woodpecker/server/model"
2016-06-25 23:45:33 +00:00
)
const (
2016-06-26 05:27:09 +00:00
requestTokenURL = "%s/plugins/servlet/oauth/request-token"
2016-06-25 23:45:33 +00:00
authorizeTokenURL = "%s/plugins/servlet/oauth/authorize"
2016-06-26 05:27:09 +00:00
accessTokenURL = "%s/plugins/servlet/oauth/access-token"
2016-04-19 04:40:49 +00:00
)
2016-05-01 23:30:00 +00:00
// Opts defines configuration options.
type Opts struct {
2017-02-01 21:49:26 +00:00
URL string // Stash server url.
Username string // Git machine account username.
Password string // Git machine account password.
ConsumerKey string // Oauth1 consumer key.
ConsumerRSA string // Oauth1 consumer key file.
2017-02-01 21:41:45 +00:00
ConsumerRSAString string
2017-02-01 21:49:26 +00:00
SkipVerify bool // Skip ssl verification.
2016-04-19 04:40:49 +00:00
}
2016-06-25 23:45:33 +00:00
type Config struct {
2016-06-26 05:27:09 +00:00
URL string
Username string
Password string
2016-06-25 23:45:33 +00:00
SkipVerify bool
2016-06-26 05:27:09 +00:00
Consumer * oauth . Consumer
2016-06-25 23:45:33 +00:00
}
2022-11-04 23:35:06 +00:00
// New returns a Forge implementation that integrates with Bitbucket Server,
2016-05-01 23:30:00 +00:00
// the on-premise edition of Bitbucket Cloud, formerly known as Stash.
2022-11-04 23:35:06 +00:00
func New ( opts Opts ) ( forge . Forge , error ) {
2016-06-25 23:45:33 +00:00
config := & Config {
2016-06-26 05:27:09 +00:00
URL : opts . URL ,
Username : opts . Username ,
Password : opts . Password ,
2016-06-25 23:45:33 +00:00
SkipVerify : opts . SkipVerify ,
2016-04-29 19:39:56 +00:00
}
2016-05-01 23:30:00 +00:00
switch {
2016-06-25 23:45:33 +00:00
case opts . Username == "" :
2016-05-01 23:30:00 +00:00
return nil , fmt . Errorf ( "Must have a git machine account username" )
2016-06-25 23:45:33 +00:00
case opts . Password == "" :
2016-05-01 23:30:00 +00:00
return nil , fmt . Errorf ( "Must have a git machine account password" )
2016-06-25 23:45:33 +00:00
case opts . ConsumerKey == "" :
2016-05-01 23:30:00 +00:00
return nil , fmt . Errorf ( "Must have a oauth1 consumer key" )
}
2016-04-19 04:40:49 +00:00
2017-02-01 21:41:45 +00:00
if opts . ConsumerRSA == "" && opts . ConsumerRSAString == "" {
return nil , fmt . Errorf ( "must have CONSUMER_RSA_KEY set to the path of a oauth1 consumer key file or CONSUMER_RSA_KEY_STRING set to the value of a oauth1 consumer key" )
2016-04-19 20:29:52 +00:00
}
2017-02-01 21:41:45 +00:00
2017-02-01 21:49:26 +00:00
var keyFileBytes [ ] byte
2017-02-01 21:41:45 +00:00
if opts . ConsumerRSA != "" {
2017-02-01 21:49:26 +00:00
var err error
2022-08-29 23:14:07 +00:00
keyFileBytes , err = os . ReadFile ( opts . ConsumerRSA )
2017-02-01 21:41:45 +00:00
if err != nil {
return nil , err
}
} else {
keyFileBytes = [ ] byte ( opts . ConsumerRSAString )
2016-04-19 20:29:52 +00:00
}
2017-02-01 21:41:45 +00:00
2017-02-01 21:49:26 +00:00
block , _ := pem . Decode ( keyFileBytes )
PrivateKey , err := x509 . ParsePKCS1PrivateKey ( block . Bytes )
if err != nil {
return nil , err
}
2017-02-01 21:41:45 +00:00
2016-06-26 05:27:09 +00:00
config . Consumer = CreateConsumer ( opts . URL , opts . ConsumerKey , PrivateKey )
2016-06-25 23:45:33 +00:00
return config , nil
2016-04-19 04:40:49 +00:00
}
2022-06-17 18:14:01 +00:00
// Name returns the string name of this driver
func ( c * Config ) Name ( ) string {
return "stash"
}
2021-09-28 10:56:59 +00:00
func ( c * Config ) Login ( ctx context . Context , res http . ResponseWriter , req * http . Request ) ( * model . User , error ) {
2021-09-24 14:29:26 +00:00
requestToken , u , err := c . Consumer . GetRequestTokenAndUrl ( "oob" )
2016-04-19 04:40:49 +00:00
if err != nil {
2016-05-01 23:30:00 +00:00
return nil , err
2016-04-19 04:40:49 +00:00
}
2022-01-05 20:50:23 +00:00
code := req . FormValue ( "oauth_verifier" )
2016-04-19 04:40:49 +00:00
if len ( code ) == 0 {
2021-09-24 14:29:26 +00:00
http . Redirect ( res , req , u , http . StatusSeeOther )
2016-05-01 23:30:00 +00:00
return nil , nil
2016-04-19 04:40:49 +00:00
}
2016-05-01 23:30:00 +00:00
requestToken . Token = req . FormValue ( "oauth_token" )
2016-06-26 05:27:09 +00:00
accessToken , err := c . Consumer . AuthorizeToken ( requestToken , code )
2016-04-20 18:59:47 +00:00
if err != nil {
2016-05-01 23:30:00 +00:00
return nil , err
2016-04-19 04:40:49 +00:00
}
2021-09-28 10:56:59 +00:00
client := internal . NewClientWithToken ( ctx , c . URL , c . Consumer , accessToken . Token )
2016-04-19 04:40:49 +00:00
2016-07-24 21:07:44 +00:00
user , err := client . FindCurrentUser ( )
if err != nil {
return nil , err
}
return convertUser ( user , accessToken ) , nil
2016-05-01 23:30:00 +00:00
}
2016-04-19 04:40:49 +00:00
2016-05-01 23:30:00 +00:00
// Auth is not supported by the Stash driver.
2021-09-28 10:56:59 +00:00
func ( * Config ) Auth ( ctx context . Context , token , secret string ) ( string , error ) {
2016-05-01 23:30:00 +00:00
return "" , fmt . Errorf ( "Not Implemented" )
2016-04-19 04:40:49 +00:00
}
2016-05-01 23:30:00 +00:00
// Teams is not supported by the Stash driver.
2021-09-28 10:56:59 +00:00
func ( * Config ) Teams ( ctx context . Context , u * model . User ) ( [ ] * model . Team , error ) {
2016-05-01 23:30:00 +00:00
var teams [ ] * model . Team
return teams , nil
2016-04-19 04:40:49 +00:00
}
2016-11-11 18:56:48 +00:00
// TeamPerm is not supported by the Stash driver.
func ( * Config ) TeamPerm ( u * model . User , org string ) ( * model . Perm , error ) {
return nil , nil
}
2022-11-04 23:35:06 +00:00
func ( c * Config ) Repo ( ctx context . Context , u * model . User , _ model . ForgeID , owner , name string ) ( * model . Repo , error ) {
2021-09-28 10:56:59 +00:00
repo , err := internal . NewClientWithToken ( ctx , c . URL , c . Consumer , u . Token ) . FindRepo ( owner , name )
2016-07-24 21:07:44 +00:00
if err != nil {
return nil , err
}
return convertRepo ( repo ) , nil
2016-06-25 23:45:33 +00:00
}
2016-04-19 04:40:49 +00:00
2021-09-28 10:56:59 +00:00
func ( c * Config ) Repos ( ctx context . Context , u * model . User ) ( [ ] * model . Repo , error ) {
repos , err := internal . NewClientWithToken ( ctx , c . URL , c . Consumer , u . Token ) . FindRepos ( )
2016-07-24 21:07:44 +00:00
if err != nil {
return nil , err
}
2017-07-14 19:58:38 +00:00
var all [ ] * model . Repo
2016-07-24 21:07:44 +00:00
for _ , repo := range repos {
2017-07-14 19:58:38 +00:00
all = append ( all , convertRepo ( repo ) )
2016-07-24 21:07:44 +00:00
}
2016-04-19 04:40:49 +00:00
2016-07-24 21:07:44 +00:00
return all , nil
2016-04-19 04:40:49 +00:00
}
2022-01-03 14:33:36 +00:00
func ( c * Config ) Perm ( ctx context . Context , u * model . User , repo * model . Repo ) ( * model . Perm , error ) {
2021-09-28 10:56:59 +00:00
client := internal . NewClientWithToken ( ctx , c . URL , c . Consumer , u . Token )
2016-06-14 03:08:56 +00:00
2022-01-03 14:33:36 +00:00
return client . FindRepoPerms ( repo . Owner , repo . Name )
2016-04-19 04:40:49 +00:00
}
2022-10-18 01:24:12 +00:00
func ( c * Config ) File ( ctx context . Context , u * model . User , r * model . Repo , p * model . Pipeline , f string ) ( [ ] byte , error ) {
2021-09-28 10:56:59 +00:00
client := internal . NewClientWithToken ( ctx , c . URL , c . Consumer , u . Token )
2016-04-19 04:40:49 +00:00
2022-10-18 01:24:12 +00:00
return client . FindFileForRepo ( r . Owner , r . Name , f , p . Ref )
2016-04-19 04:40:49 +00:00
}
2022-11-06 11:44:04 +00:00
func ( c * Config ) Dir ( ctx context . Context , u * model . User , r * model . Repo , p * model . Pipeline , f string ) ( [ ] * forge_types . FileMeta , error ) {
return nil , forge_types . ErrNotImplemented
2019-06-03 07:16:15 +00:00
}
2016-06-26 05:27:09 +00:00
// Status is not supported by the bitbucketserver driver.
2022-10-28 15:38:53 +00:00
func ( c * Config ) Status ( ctx context . Context , user * model . User , repo * model . Repo , pipeline * model . Pipeline , step * model . Step ) error {
2022-10-18 01:24:12 +00:00
status := internal . PipelineStatus {
State : convertStatus ( pipeline . Status ) ,
Desc : common . GetPipelineStatusDescription ( pipeline . Status ) ,
Name : fmt . Sprintf ( "Woodpecker #%d - %s" , pipeline . Number , pipeline . Branch ) ,
2021-10-02 08:59:34 +00:00
Key : "Woodpecker" ,
2022-10-18 01:24:12 +00:00
URL : common . GetPipelineStatusLink ( repo , pipeline , nil ) ,
2016-08-14 02:06:15 +00:00
}
2021-12-28 16:02:49 +00:00
client := internal . NewClientWithToken ( ctx , c . URL , c . Consumer , user . Token )
2016-08-14 02:06:15 +00:00
2022-10-18 01:24:12 +00:00
return client . CreateStatus ( pipeline . Commit , & status )
2016-04-19 04:40:49 +00:00
}
2016-06-25 23:45:33 +00:00
func ( c * Config ) Netrc ( user * model . User , r * model . Repo ) ( * model . Netrc , error ) {
2022-02-26 01:54:15 +00:00
host , err := common . ExtractHostFromCloneURL ( r . Clone )
2016-06-13 05:18:31 +00:00
if err != nil {
return nil , err
}
2016-06-12 01:42:55 +00:00
2016-04-19 04:40:49 +00:00
return & model . Netrc {
2016-06-25 23:45:33 +00:00
Login : c . Username ,
Password : c . Password ,
2022-02-26 01:54:15 +00:00
Machine : host ,
2016-04-19 04:40:49 +00:00
} , nil
}
2021-09-28 10:56:59 +00:00
func ( c * Config ) Activate ( ctx context . Context , u * model . User , r * model . Repo , link string ) error {
client := internal . NewClientWithToken ( ctx , c . URL , c . Consumer , u . Token )
2016-06-25 23:45:33 +00:00
return client . CreateHook ( r . Owner , r . Name , link )
2016-04-19 04:40:49 +00:00
}
2021-10-27 00:47:55 +00:00
// Branches returns the names of all branches for the named repository.
func ( c * Config ) Branches ( ctx context . Context , u * model . User , r * model . Repo ) ( [ ] string , error ) {
2022-05-12 17:31:01 +00:00
bitbucketBranches , err := internal . NewClientWithToken ( ctx , c . URL , c . Consumer , u . Token ) . ListBranches ( r . Owner , r . Name )
if err != nil {
return nil , err
}
branches := make ( [ ] string , 0 )
for _ , branch := range bitbucketBranches {
branches = append ( branches , branch . Name )
}
return branches , nil
2021-10-27 00:47:55 +00:00
}
2022-11-09 07:12:17 +00:00
// BranchHead returns the sha of the head (latest commit) of the specified branch
2022-08-31 22:36:32 +00:00
func ( c * Config ) BranchHead ( ctx context . Context , u * model . User , r * model . Repo , branch string ) ( string , error ) {
// TODO(1138): missing implementation
2022-11-06 11:44:04 +00:00
return "" , forge_types . ErrNotImplemented
2022-08-31 22:36:32 +00:00
}
2021-09-28 10:56:59 +00:00
func ( c * Config ) Deactivate ( ctx context . Context , u * model . User , r * model . Repo , link string ) error {
client := internal . NewClientWithToken ( ctx , c . URL , c . Consumer , u . Token )
2016-06-25 23:45:33 +00:00
return client . DeleteHook ( r . Owner , r . Name , link )
2016-04-19 04:40:49 +00:00
}
2022-10-18 01:24:12 +00:00
func ( c * Config ) Hook ( ctx context . Context , r * http . Request ) ( * model . Repo , * model . Pipeline , error ) {
2016-08-11 20:35:47 +00:00
return parseHook ( r , c . URL )
2016-04-19 04:40:49 +00:00
}
2022-07-25 01:09:35 +00:00
// OrgMembership returns if user is member of organization and if user
// is admin/owner in this organization.
func ( c * Config ) OrgMembership ( ctx context . Context , u * model . User , owner string ) ( * model . OrgPerm , error ) {
// TODO: Not implemented currently
return nil , nil
}
2022-01-05 20:50:23 +00:00
func CreateConsumer ( URL , ConsumerKey string , PrivateKey * rsa . PrivateKey ) * oauth . Consumer {
2016-06-25 23:45:33 +00:00
consumer := oauth . NewRSAConsumer (
2016-06-26 05:27:09 +00:00
ConsumerKey ,
PrivateKey ,
2016-06-25 23:45:33 +00:00
oauth . ServiceProvider {
2016-06-26 05:27:09 +00:00
RequestTokenUrl : fmt . Sprintf ( requestTokenURL , URL ) ,
AuthorizeTokenUrl : fmt . Sprintf ( authorizeTokenURL , URL ) ,
AccessTokenUrl : fmt . Sprintf ( accessTokenURL , URL ) ,
2016-06-25 23:45:33 +00:00
HttpMethod : "POST" ,
} )
consumer . HttpClient = & http . Client {
Transport : & http . Transport {
TLSClientConfig : & tls . Config { InsecureSkipVerify : true } ,
2017-07-14 19:58:38 +00:00
Proxy : http . ProxyFromEnvironment ,
2016-06-25 23:45:33 +00:00
} ,
2016-06-14 03:08:56 +00:00
}
2016-06-25 23:45:33 +00:00
return consumer
}