blob: 6f92791f17532e3d23e3897b77f7448a0ca3e4cf [file] [log] [blame]
// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"flag"
"fmt"
yaml "gopkg.in/yaml.v2"
"upspin.io/factotum"
"upspin.io/key/usercache"
"upspin.io/upspin"
"upspin.io/user"
)
func (s *State) user(args ...string) {
const help = `
User prints in YAML format the user record stored in the key server
for the specified user, by default the current user.
With the -put flag, user writes or replaces the information stored
for the current user, such as to update keys or server information.
The information is read from standard input or from the file provided
with the -in flag. The input must provide the complete record for
the user, and must be in the same YAML format printed by the command
without the -put flag.
When using -put, the command takes no arguments. The name of the
user whose record is to be updated must be provided in the input
record and must either be the current user or the name of another
user whose domain is administered by the current user.
A handy way to use the command is to edit the config file and run
upspin user | upspin user -put
To install new users see the signup command.
`
fs := flag.NewFlagSet("user", flag.ExitOnError)
put := fs.Bool("put", false, "write new user record")
inFile := fs.String("in", "", "input file (default standard input)")
force := fs.Bool("force", false, "force writing user record even if key is empty")
s.ParseFlags(fs, args, help, "user [username...]\n user -put [-in=inputfile] [-force] [username]")
keyServer := s.KeyServer()
if *put {
s.putUser(fs, keyServer, s.GlobOneLocal(*inFile), *force)
return
}
if *inFile != "" {
s.Exitf("-in only available with -put")
}
if *force {
s.Exitf("-force only available with -put")
}
var userNames []upspin.UserName
if fs.NArg() == 0 {
userNames = append(userNames, s.Config.UserName())
} else {
for i := 0; i < fs.NArg(); i++ {
userName, err := user.Clean(upspin.UserName(fs.Arg(i)))
if err != nil {
s.Exit(err)
}
userNames = append(userNames, userName)
}
}
for _, name := range userNames {
u, err := keyServer.Lookup(name)
if err != nil {
s.Exit(err)
}
blob, err := yaml.Marshal(u)
if err != nil {
// TODO(adg): better error message?
s.Exit(err)
}
s.Printf("%s\n", blob)
if name != s.Config.UserName() {
continue
}
// When it's the user asking about herself, the result comes
// from the configuration and may disagree with the value in the
// key store. This is a common source of error so we want to
// diagnose it. To do that, we wipe the key cache and go again.
// This will wipe the memory of our remembered configuration and
// reload it from the key server.
usercache.ResetGlobal()
keyU, err := keyServer.Lookup(name)
if err != nil {
s.Exit(err)
}
var buf bytes.Buffer
if keyU.Name != u.Name {
fmt.Fprintf(&buf, "user name in configuration: %s\n", u.Name)
fmt.Fprintf(&buf, "user name in key server: %s\n", keyU.Name)
}
if keyU.PublicKey != u.PublicKey {
fmt.Fprintf(&buf, "public key in configuration does not match key server\n")
}
// There must be dir servers defined in both and we expect agreement.
if !equalEndpoints(keyU.Dirs, u.Dirs) {
fmt.Fprintf(&buf, "dirs in configuration: %s\n", u.Dirs)
fmt.Fprintf(&buf, "dirs in key server: %s\n", keyU.Dirs)
}
// Remote stores need not be defined (yet).
if len(keyU.Stores) > 0 && !equalEndpoints(keyU.Stores, u.Stores) {
fmt.Fprintf(&buf, "stores in configuration: %s\n", u.Stores)
fmt.Fprintf(&buf, "stores in key server: %s\n", keyU.Stores)
}
if buf.Len() > 0 {
s.Exitf("local configuration differs from public record in key server:\n%s", &buf)
}
}
}
func equalEndpoints(a, b []upspin.Endpoint) bool {
if len(a) != len(b) {
return false
}
for i, e := range a {
if e != b[i] {
return false
}
}
return true
}
func (s *State) putUser(fs *flag.FlagSet, keyServer upspin.KeyServer, inFile string, force bool) {
data := s.ReadAll(inFile)
userStruct := new(upspin.User)
err := yaml.Unmarshal(data, userStruct)
if err != nil {
// TODO(adg): better error message?
s.Exit(err)
}
if fs.NArg() != 0 && upspin.UserName(fs.Arg(0)) != userStruct.Name {
s.Exitf("User name provided does not match the one read from the input file.")
}
// Validate public key.
if userStruct.PublicKey == "" && !force {
s.Exitf("An empty public key will prevent user from accessing services. To override use -force.")
}
_, err = factotum.ParsePublicKey(userStruct.PublicKey)
if err != nil && !force {
s.Exitf("invalid public key, to override use -force: %s", err.Error())
}
// Clean the username.
userStruct.Name, err = user.Clean(userStruct.Name)
if err != nil {
s.Exit(err)
}
err = keyServer.Put(userStruct)
if err != nil {
s.Exit(err)
}
}