github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/migration/migrations/1528470872_add_global_users.up.go (about)

     1  package migrations
     2  
     3  import (
     4  	"database/sql"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/mitchellh/mapstructure"
    11  )
    12  
    13  func (self *migrations) Up_1528470872() error {
    14  
    15  	type team struct {
    16  		id    int64
    17  		name  string
    18  		auth  []byte
    19  		nonce sql.NullString
    20  	}
    21  
    22  	tx, err := self.DB.Begin()
    23  	if err != nil {
    24  		return err
    25  	}
    26  
    27  	_, err = tx.Exec("ALTER TABLE teams RENAME COLUMN auth TO legacy_auth")
    28  	if err != nil {
    29  		tx.Rollback()
    30  		return err
    31  	}
    32  
    33  	_, err = tx.Exec("ALTER TABLE teams ADD COLUMN auth text")
    34  	if err != nil {
    35  		tx.Rollback()
    36  		return err
    37  	}
    38  
    39  	rows, err := tx.Query("SELECT id, name, legacy_auth, nonce FROM teams")
    40  	if err != nil {
    41  		tx.Rollback()
    42  		return err
    43  	}
    44  
    45  	teams := []team{}
    46  
    47  	for rows.Next() {
    48  		team := team{}
    49  
    50  		if err = rows.Scan(&team.id, &team.name, &team.auth, &team.nonce); err != nil {
    51  			tx.Rollback()
    52  			return err
    53  		}
    54  
    55  		teams = append(teams, team)
    56  	}
    57  
    58  	mustBeUniqueAmongstAllTeams := map[string]map[string]map[string][]string{
    59  		"basicauth": map[string]map[string][]string{
    60  			"username": map[string][]string{},
    61  		},
    62  	}
    63  
    64  	mustBeSameAmongstAllTeams := map[string]map[string]map[string][]string{
    65  		"github": map[string]map[string][]string{
    66  			"auth_url":  map[string][]string{},
    67  			"token_url": map[string][]string{},
    68  			"api_url":   map[string][]string{},
    69  		},
    70  		"uaa": map[string]map[string][]string{
    71  			"auth_url":  map[string][]string{},
    72  			"token_url": map[string][]string{},
    73  			"cf_url":    map[string][]string{},
    74  		},
    75  		"gitlab": map[string]map[string][]string{
    76  			"auth_url":  map[string][]string{},
    77  			"token_url": map[string][]string{},
    78  			"api_url":   map[string][]string{},
    79  		},
    80  		"oauth": map[string]map[string][]string{
    81  			"auth_url":  map[string][]string{},
    82  			"token_url": map[string][]string{},
    83  		},
    84  		"oauth_oidc": map[string]map[string][]string{
    85  			"auth_url":  map[string][]string{},
    86  			"token_url": map[string][]string{},
    87  		},
    88  	}
    89  
    90  	for _, team := range teams {
    91  
    92  		var noncense *string
    93  		if team.nonce.Valid {
    94  			noncense = &team.nonce.String
    95  		}
    96  
    97  		decryptedAuth, err := self.Strategy.Decrypt(string(team.auth), noncense)
    98  		if err != nil {
    99  			tx.Rollback()
   100  			return err
   101  		}
   102  
   103  		var authConfig map[string]interface{}
   104  		if err = json.Unmarshal(decryptedAuth, &authConfig); err != nil {
   105  			tx.Rollback()
   106  			return err
   107  		}
   108  
   109  		if authConfig == nil {
   110  			authConfig = map[string]interface{}{}
   111  		}
   112  
   113  		newGroups := []string{}
   114  		newUsers := []string{}
   115  
   116  		for provider, rawConfig := range authConfig {
   117  
   118  			for key, set := range mustBeSameAmongstAllTeams[provider] {
   119  				if parsedConfig, ok := rawConfig.(map[string]interface{}); ok {
   120  					if value, ok := parsedConfig[key].(string); ok {
   121  						_, valuePresent := set[value]
   122  						if valuePresent {
   123  							set[value] = append(set[value], team.name)
   124  						} else {
   125  							set[value] = []string{team.name}
   126  						}
   127  					}
   128  				}
   129  			}
   130  
   131  			for key, set := range mustBeUniqueAmongstAllTeams[provider] {
   132  				if parsedConfig, ok := rawConfig.(map[string]interface{}); ok {
   133  					if value, parseOk := parsedConfig[key].(string); parseOk {
   134  						_, valuePresent := set[value]
   135  						if valuePresent {
   136  							set[value] = append(set[value], team.name)
   137  						} else {
   138  							set[value] = []string{team.name}
   139  						}
   140  					}
   141  				}
   142  			}
   143  
   144  			switch provider {
   145  			case "github":
   146  				var config struct {
   147  					Organizations []string `mapstructure:"organizations"`
   148  					Teams         []struct {
   149  						OrganizationName string `mapstructure:"organization_name"`
   150  						TeamName         string `mapstructure:"team_name"`
   151  					} `mapstructure:"teams"`
   152  					Users []string `mapstructure:"users"`
   153  				}
   154  				if err = mapstructure.Decode(rawConfig, &config); err != nil {
   155  					tx.Rollback()
   156  					return err
   157  				}
   158  
   159  				for _, team := range config.Teams {
   160  					newGroups = append(newGroups, provider+":"+team.OrganizationName+":"+team.TeamName)
   161  				}
   162  				for _, org := range config.Organizations {
   163  					newGroups = append(newGroups, provider+":"+org)
   164  				}
   165  				for _, user := range config.Users {
   166  					newUsers = append(newUsers, provider+":"+user)
   167  				}
   168  
   169  			case "basicauth":
   170  				var config struct {
   171  					Username string `mapstructure:"username"`
   172  				}
   173  				if err = mapstructure.Decode(rawConfig, &config); err != nil {
   174  					tx.Rollback()
   175  					return err
   176  				}
   177  
   178  				newUsers = append(newUsers, "local:"+config.Username)
   179  
   180  			case "uaa":
   181  				var config struct {
   182  					Spaces []string `mapstructure:"cf_spaces"`
   183  				}
   184  				if err = mapstructure.Decode(rawConfig, &config); err != nil {
   185  					tx.Rollback()
   186  					return err
   187  				}
   188  
   189  				for _, space := range config.Spaces {
   190  					newGroups = append(newGroups, "cf:"+space)
   191  				}
   192  
   193  			case "gitlab":
   194  				var config struct {
   195  					Groups []string `mapstructure:"groups"`
   196  				}
   197  				if err = mapstructure.Decode(rawConfig, &config); err != nil {
   198  					tx.Rollback()
   199  					return err
   200  				}
   201  
   202  				for _, group := range config.Groups {
   203  					newGroups = append(newGroups, "gitlab:"+group)
   204  				}
   205  
   206  			case "oauth":
   207  				var config struct {
   208  					Scope string `mapstructure:"scope"`
   209  				}
   210  				if err = mapstructure.Decode(rawConfig, &config); err != nil {
   211  					tx.Rollback()
   212  					return err
   213  				}
   214  
   215  				newGroups = append(newGroups, "oauth:"+config.Scope)
   216  
   217  			case "oauth_oidc":
   218  				var config struct {
   219  					UserID []string `mapstructure:"user_id"`
   220  					Groups []string `mapstructure:"groups"`
   221  				}
   222  				if err = mapstructure.Decode(rawConfig, &config); err != nil {
   223  					tx.Rollback()
   224  					return err
   225  				}
   226  
   227  				for _, user := range config.UserID {
   228  					newUsers = append(newUsers, "oidc:"+user)
   229  				}
   230  				for _, group := range config.Groups {
   231  					newGroups = append(newGroups, "oidc:"+group)
   232  				}
   233  
   234  			case "bitbucket-server", "bitbucket-cloud":
   235  				tx.Rollback()
   236  				return errors.New("Bitbucket is no longer supported")
   237  			}
   238  		}
   239  
   240  		newAuth, err := json.Marshal(map[string][]string{
   241  			"users":  newUsers,
   242  			"groups": newGroups,
   243  		})
   244  		if err != nil {
   245  			tx.Rollback()
   246  			return err
   247  		}
   248  
   249  		_, err = tx.Exec("UPDATE teams SET auth = $1 WHERE id = $2", newAuth, team.id)
   250  		if err != nil {
   251  			tx.Rollback()
   252  			return err
   253  		}
   254  	}
   255  
   256  	errorMessage := ""
   257  	for provider, keys := range mustBeSameAmongstAllTeams {
   258  		for key, values := range keys {
   259  			if len(values) > 1 {
   260  				errorMessage += fmt.Sprintf("Non-unique value of '%s' for auth provider '%s' breaks migration: ", key, provider)
   261  				offendingTeams := []string{}
   262  				for value, teams := range values {
   263  					offendingTeams = append(offendingTeams, fmt.Sprintf("teams %v have value '%s'", teams, value))
   264  				}
   265  				errorMessage += strings.Join(offendingTeams, ", ")
   266  				errorMessage += "\n"
   267  			}
   268  		}
   269  	}
   270  	for provider, keys := range mustBeUniqueAmongstAllTeams {
   271  		for key, values := range keys {
   272  			for value, teams := range values {
   273  				if len(teams) > 1 {
   274  					errorMessage += fmt.Sprintf("Multiple teams having the same value, '%s', of '%s' for auth provider '%s' breaks migration. Offending teams: %v\n", value, key, provider, teams)
   275  				}
   276  			}
   277  		}
   278  	}
   279  	if errorMessage != "" {
   280  		tx.Rollback()
   281  		return fmt.Errorf("Problems in your database caused the migration to fail:\n\n%s", errorMessage)
   282  	}
   283  
   284  	err = tx.Commit()
   285  	if err != nil {
   286  		tx.Rollback()
   287  		return err
   288  	}
   289  
   290  	return nil
   291  }