github.com/cyverse/go-irodsclient@v0.13.2/irods/types/account.go (about)

     1  package types
     2  
     3  import (
     4  	"regexp"
     5  
     6  	"github.com/cyverse/go-irodsclient/irods/common"
     7  	"golang.org/x/xerrors"
     8  	"gopkg.in/yaml.v3"
     9  )
    10  
    11  const (
    12  	// PamTTLDefault is a default value for Pam TTL
    13  	PamTTLDefault       int    = 1
    14  	UsernameRegexString string = "^((\\w|[-.@])+)$"
    15  )
    16  
    17  // IRODSAccount contains irods login information
    18  type IRODSAccount struct {
    19  	AuthenticationScheme    AuthScheme
    20  	ClientServerNegotiation bool
    21  	CSNegotiationPolicy     CSNegotiationRequire
    22  	Host                    string
    23  	Port                    int
    24  	ClientUser              string
    25  	ClientZone              string
    26  	ProxyUser               string
    27  	ProxyZone               string
    28  	Password                string
    29  	Ticket                  string
    30  	DefaultResource         string
    31  	PamTTL                  int
    32  	SSLConfiguration        *IRODSSSLConfig
    33  }
    34  
    35  // CreateIRODSAccount creates IRODSAccount
    36  func CreateIRODSAccount(host string, port int, user string, zone string,
    37  	authScheme AuthScheme, password string, defaultResource string) (*IRODSAccount, error) {
    38  	account := &IRODSAccount{
    39  		AuthenticationScheme:    authScheme,
    40  		ClientServerNegotiation: false,
    41  		CSNegotiationPolicy:     CSNegotiationDontCare,
    42  		Host:                    host,
    43  		Port:                    port,
    44  		ClientUser:              user,
    45  		ClientZone:              zone,
    46  		ProxyUser:               user,
    47  		ProxyZone:               zone,
    48  		Password:                password,
    49  		Ticket:                  "",
    50  		DefaultResource:         defaultResource,
    51  		PamTTL:                  PamTTLDefault,
    52  		SSLConfiguration:        nil,
    53  	}
    54  
    55  	account.FixAuthConfiguration()
    56  
    57  	return account, nil
    58  }
    59  
    60  // CreateIRODSAccountForTicket creates IRODSAccount
    61  func CreateIRODSAccountForTicket(host string, port int, user string, zone string,
    62  	authScheme AuthScheme, password string, ticket string, defaultResource string) (*IRODSAccount, error) {
    63  	account := &IRODSAccount{
    64  		AuthenticationScheme:    authScheme,
    65  		ClientServerNegotiation: false,
    66  		CSNegotiationPolicy:     CSNegotiationDontCare,
    67  		Host:                    host,
    68  		Port:                    port,
    69  		ClientUser:              user,
    70  		ClientZone:              zone,
    71  		ProxyUser:               user,
    72  		ProxyZone:               zone,
    73  		Password:                password,
    74  		Ticket:                  ticket,
    75  		DefaultResource:         defaultResource,
    76  		PamTTL:                  PamTTLDefault,
    77  		SSLConfiguration:        nil,
    78  	}
    79  
    80  	account.FixAuthConfiguration()
    81  
    82  	return account, nil
    83  }
    84  
    85  // CreateIRODSProxyAccount creates IRODSAccount for proxy access
    86  func CreateIRODSProxyAccount(host string, port int, clientUser string, clientZone string,
    87  	proxyUser string, proxyZone string,
    88  	authScheme AuthScheme, password string, defaultResource string) (*IRODSAccount, error) {
    89  	account := &IRODSAccount{
    90  		AuthenticationScheme:    authScheme,
    91  		ClientServerNegotiation: false,
    92  		CSNegotiationPolicy:     CSNegotiationDontCare,
    93  		Host:                    host,
    94  		Port:                    port,
    95  		ClientUser:              clientUser,
    96  		ClientZone:              clientZone,
    97  		ProxyUser:               proxyUser,
    98  		ProxyZone:               proxyZone,
    99  		Password:                password,
   100  		Ticket:                  "",
   101  		DefaultResource:         defaultResource,
   102  		PamTTL:                  PamTTLDefault,
   103  		SSLConfiguration:        nil,
   104  	}
   105  
   106  	account.FixAuthConfiguration()
   107  
   108  	return account, nil
   109  }
   110  
   111  // CreateIRODSAccountFromYAML creates IRODSAccount from YAML
   112  func CreateIRODSAccountFromYAML(yamlBytes []byte) (*IRODSAccount, error) {
   113  	y := make(map[string]interface{})
   114  
   115  	err := yaml.Unmarshal(yamlBytes, &y)
   116  	if err != nil {
   117  		return nil, xerrors.Errorf("failed to unmarshal yaml to map: %w", err)
   118  	}
   119  
   120  	authScheme := AuthSchemeNative
   121  	if val, ok := y["auth_scheme"]; ok {
   122  		authScheme, err = GetAuthScheme(val.(string))
   123  		if err != nil {
   124  			authScheme = AuthSchemeNative
   125  		}
   126  	}
   127  
   128  	csNegotiation := false
   129  	if val, ok := y["cs_negotiation"]; ok {
   130  		csNegotiation = val.(bool)
   131  	}
   132  
   133  	csNegotiationPolicy := CSNegotiationDontCare
   134  	if val, ok := y["cs_negotiation_policy"]; ok {
   135  		csNegotiationPolicy, err = GetCSNegotiationRequire(val.(string))
   136  		if err != nil {
   137  			csNegotiationPolicy = CSNegotiationDontCare
   138  		}
   139  	}
   140  
   141  	host := make(map[string]interface{})
   142  	if val, ok := y["host"]; ok {
   143  		host = val.(map[string]interface{})
   144  	}
   145  
   146  	hostname := ""
   147  	if val, ok := host["hostname"]; ok {
   148  		hostname = val.(string)
   149  	}
   150  
   151  	port := 1247
   152  	if val, ok := host["port"]; ok {
   153  		port = val.(int)
   154  	}
   155  
   156  	defaultResource := ""
   157  	if val, ok := y["default_resource"]; ok {
   158  		defaultResource = val.(string)
   159  	}
   160  
   161  	// proxy user
   162  	proxyUser := make(map[string]interface{})
   163  	if val, ok := y["proxy_user"]; ok {
   164  		proxyUser = val.(map[string]interface{})
   165  	}
   166  
   167  	proxyUsername := ""
   168  	if val, ok := proxyUser["username"]; ok {
   169  		proxyUsername = val.(string)
   170  	}
   171  
   172  	proxyPassword := ""
   173  	if val, ok := proxyUser["password"]; ok {
   174  		proxyPassword = val.(string)
   175  	}
   176  
   177  	proxyZone := ""
   178  	if val, ok := proxyUser["zone"]; ok {
   179  		proxyZone = val.(string)
   180  	}
   181  
   182  	ticket := ""
   183  	if val, ok := proxyUser["ticket"]; ok {
   184  		ticket = val.(string)
   185  	}
   186  
   187  	// client user
   188  	clientUser := make(map[string]interface{})
   189  	if val, ok := y["client_user"]; ok {
   190  		clientUser = val.(map[string]interface{})
   191  	}
   192  
   193  	clientUsername := ""
   194  	if val, ok := clientUser["username"]; ok {
   195  		clientUsername = val.(string)
   196  	}
   197  
   198  	clientZone := ""
   199  	if val, ok := clientUser["zone"]; ok {
   200  		clientZone = val.(string)
   201  	}
   202  
   203  	if val, ok := clientUser["ticket"]; ok {
   204  		ticket = val.(string)
   205  	}
   206  
   207  	// normal user
   208  	user := make(map[string]interface{})
   209  	if val, ok := y["user"]; ok {
   210  		user = val.(map[string]interface{})
   211  	}
   212  
   213  	if val, ok := user["username"]; ok {
   214  		proxyUsername = val.(string)
   215  		clientUsername = proxyUsername
   216  
   217  	}
   218  
   219  	if val, ok := user["password"]; ok {
   220  		proxyPassword = val.(string)
   221  	}
   222  
   223  	if val, ok := user["zone"]; ok {
   224  		proxyZone = val.(string)
   225  		clientZone = proxyZone
   226  	}
   227  
   228  	if val, ok := user["ticket"]; ok {
   229  		ticket = val.(string)
   230  	}
   231  
   232  	// PAM Configuration
   233  	pamConfig := make(map[string]interface{})
   234  	if val, ok := y["pam"]; ok {
   235  		pamConfig = val.(map[string]interface{})
   236  	}
   237  
   238  	pamTTL := 0
   239  	if val, ok := pamConfig["ttl"]; ok {
   240  		pamTTL = val.(int)
   241  	}
   242  
   243  	// SSL Configuration
   244  	hasSSLConfig := false
   245  	sslConfig := make(map[string]interface{})
   246  	if val, ok := y["ssl"]; ok {
   247  		sslConfig = val.(map[string]interface{})
   248  		hasSSLConfig = true
   249  	}
   250  
   251  	caCert := ""
   252  	if val, ok := sslConfig["ca_cert_file"]; ok {
   253  		caCert = val.(string)
   254  	}
   255  
   256  	keySize := 0
   257  	if val, ok := sslConfig["key_size"]; ok {
   258  		keySize = val.(int)
   259  	}
   260  
   261  	algorithm := ""
   262  	if val, ok := sslConfig["algorithm"]; ok {
   263  		algorithm = val.(string)
   264  	}
   265  
   266  	saltSize := 0
   267  	if val, ok := sslConfig["salt_size"]; ok {
   268  		saltSize = val.(int)
   269  	}
   270  
   271  	hashRounds := 0
   272  	if val, ok := sslConfig["hash_rounds"]; ok {
   273  		hashRounds = val.(int)
   274  	}
   275  
   276  	var irodsSSLConfig *IRODSSSLConfig = nil
   277  	if hasSSLConfig {
   278  		irodsSSLConfig, err = CreateIRODSSSLConfig(caCert, keySize, algorithm, saltSize, hashRounds)
   279  		if err != nil {
   280  			return nil, xerrors.Errorf("failed to create irods ssl config: %w", err)
   281  		}
   282  	}
   283  
   284  	account := &IRODSAccount{
   285  		AuthenticationScheme:    authScheme,
   286  		ClientServerNegotiation: csNegotiation,
   287  		CSNegotiationPolicy:     csNegotiationPolicy,
   288  		Host:                    hostname,
   289  		Port:                    port,
   290  		ClientUser:              clientUsername,
   291  		ClientZone:              clientZone,
   292  		ProxyUser:               proxyUsername,
   293  		ProxyZone:               proxyZone,
   294  		Password:                proxyPassword,
   295  		Ticket:                  ticket,
   296  		DefaultResource:         defaultResource,
   297  		PamTTL:                  pamTTL,
   298  		SSLConfiguration:        irodsSSLConfig,
   299  	}
   300  
   301  	account.FixAuthConfiguration()
   302  
   303  	return account, nil
   304  }
   305  
   306  // SetSSLConfiguration sets SSL Configuration
   307  func (account *IRODSAccount) SetSSLConfiguration(sslConf *IRODSSSLConfig) {
   308  	account.SSLConfiguration = sslConf
   309  }
   310  
   311  // SetCSNegotiation sets CSNegotiation policy
   312  func (account *IRODSAccount) SetCSNegotiation(requireNegotiation bool, requirePolicy CSNegotiationRequire) {
   313  	account.ClientServerNegotiation = requireNegotiation
   314  	account.CSNegotiationPolicy = requirePolicy
   315  
   316  	account.FixAuthConfiguration()
   317  }
   318  
   319  // UseProxyAccess returns whether it uses proxy access or not
   320  func (account *IRODSAccount) UseProxyAccess() bool {
   321  	return len(account.ProxyUser) > 0 && len(account.ClientUser) > 0 && account.ProxyUser != account.ClientUser
   322  }
   323  
   324  // UseTicket returns whether it uses ticket for access control
   325  func (account *IRODSAccount) UseTicket() bool {
   326  	return len(account.Ticket) > 0
   327  }
   328  
   329  // MaskSensitiveData returns IRODSAccount object with sensitive data masked
   330  func (account *IRODSAccount) MaskSensitiveData() *IRODSAccount {
   331  	maskedAccount := *account
   332  	maskedAccount.Password = "<password masked>"
   333  	maskedAccount.Ticket = "<ticket masked>"
   334  	return &maskedAccount
   335  }
   336  
   337  // Validate validates iRODS account
   338  func (account *IRODSAccount) Validate() error {
   339  	if len(account.Host) == 0 {
   340  		return xerrors.Errorf("empty host")
   341  	}
   342  
   343  	if account.Port <= 0 {
   344  		return xerrors.Errorf("empty port")
   345  	}
   346  
   347  	if len(account.ProxyUser) == 0 {
   348  		return xerrors.Errorf("empty user")
   349  	}
   350  
   351  	err := account.validateUsername(account.ProxyUser)
   352  	if err != nil {
   353  		return xerrors.Errorf("failed to validate username %s: %w", account.ProxyUser, err)
   354  	}
   355  
   356  	if len(account.ClientUser) > 0 {
   357  		err = account.validateUsername(account.ClientUser)
   358  		if err != nil {
   359  			return xerrors.Errorf("failed to validate username %s: %w", account.ProxyUser, err)
   360  		}
   361  	}
   362  
   363  	if len(account.ProxyZone) == 0 {
   364  		return xerrors.Errorf("empty zone")
   365  	}
   366  
   367  	if len(account.AuthenticationScheme) == 0 {
   368  		return xerrors.Errorf("empty authentication scheme")
   369  	}
   370  
   371  	if account.AuthenticationScheme != AuthSchemeNative && account.CSNegotiationPolicy != CSNegotiationRequireSSL {
   372  		return xerrors.Errorf("SSL is required for non-native authentication scheme")
   373  	}
   374  
   375  	if account.CSNegotiationPolicy == CSNegotiationRequireSSL && !account.ClientServerNegotiation {
   376  		return xerrors.Errorf("client-server negotiation is required for SSL")
   377  	}
   378  
   379  	if account.CSNegotiationPolicy == CSNegotiationRequireSSL && account.SSLConfiguration == nil {
   380  		return xerrors.Errorf("SSL configuration is empty")
   381  	}
   382  
   383  	return nil
   384  }
   385  
   386  func (account *IRODSAccount) validateUsername(username string) error {
   387  	if len(username) >= common.MaxNameLength {
   388  		return xerrors.Errorf("username too long")
   389  	}
   390  
   391  	if username == "." || username == ".." {
   392  		return xerrors.Errorf("invalid username")
   393  	}
   394  
   395  	usernameRegEx, err := regexp.Compile(UsernameRegexString)
   396  	if err != nil {
   397  		return xerrors.Errorf("failed to compile regex: %w", err)
   398  	}
   399  
   400  	if !usernameRegEx.Match([]byte(username)) {
   401  		return xerrors.Errorf("invalid username, containing invalid chars")
   402  	}
   403  	return nil
   404  }
   405  
   406  func (account *IRODSAccount) FixAuthConfiguration() {
   407  	if account.AuthenticationScheme != AuthSchemeNative {
   408  		account.CSNegotiationPolicy = CSNegotiationRequireSSL
   409  	}
   410  
   411  	if account.CSNegotiationPolicy == CSNegotiationRequireSSL {
   412  		account.ClientServerNegotiation = true
   413  	}
   414  }
   415  
   416  func (account *IRODSAccount) GetRedacted() *IRODSAccount {
   417  	account2 := IRODSAccount{}
   418  	account2 = *account
   419  	account2.Password = "<Redacted>"
   420  	return &account2
   421  }