Skip to content

Commit

Permalink
Merge pull request canonical#1631 from chipaca/chown
Browse files Browse the repository at this point in the history
client, osutil: chown the auth file
  • Loading branch information
niemeyer authored Aug 6, 2016
2 parents a8ea238 + b41641e commit b507179
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 178 deletions.
18 changes: 10 additions & 8 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type clientSuite struct {
var _ = check.Suite(&clientSuite{})

func (cs *clientSuite) SetUpTest(c *check.C) {
os.Setenv(client.TestAuthFileEnvKey, filepath.Join(c.MkDir(), "auth.json"))
cs.cli = client.New(nil)
cs.cli.SetDoer(cs)
cs.err = nil
Expand All @@ -66,6 +67,10 @@ func (cs *clientSuite) SetUpTest(c *check.C) {
dirs.SetRootDir(c.MkDir())
}

func (cs *clientSuite) TearDownTest(c *check.C) {
os.Unsetenv(client.TestAuthFileEnvKey)
}

func (cs *clientSuite) Do(req *http.Request) (*http.Response, error) {
cs.req = req
rsp := &http.Response{
Expand Down Expand Up @@ -109,22 +114,19 @@ func (cs *clientSuite) TestClientWorks(c *check.C) {
}

func (cs *clientSuite) TestClientDefaultsToNoAuthorization(c *check.C) {
home := os.Getenv("HOME")
tmpdir := c.MkDir()
os.Setenv("HOME", tmpdir)
defer os.Setenv("HOME", home)
os.Setenv(client.TestAuthFileEnvKey, filepath.Join(c.MkDir(), "json"))
defer os.Unsetenv(client.TestAuthFileEnvKey)

var v string
_ = cs.cli.Do("GET", "/this", nil, nil, &v)
c.Assert(cs.req, check.NotNil)
authorization := cs.req.Header.Get("Authorization")
c.Check(authorization, check.Equals, "")
}

func (cs *clientSuite) TestClientSetsAuthorization(c *check.C) {
home := os.Getenv("HOME")
tmpdir := c.MkDir()
os.Setenv("HOME", tmpdir)
defer os.Setenv("HOME", home)
os.Setenv(client.TestAuthFileEnvKey, filepath.Join(c.MkDir(), "json"))
defer os.Unsetenv(client.TestAuthFileEnvKey)

mockUserData := client.User{
Macaroon: "macaroon",
Expand Down
2 changes: 2 additions & 0 deletions client/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ var ParseErrorInTest = parseError
var TestWriteAuth = writeAuthData
var TestReadAuth = readAuthData
var TestStoreAuthFilename = storeAuthDataFilename

var TestAuthFileEnvKey = authFileEnvKey
50 changes: 37 additions & 13 deletions client/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"

"github.com/snapcore/snapd/osutil"
)
Expand Down Expand Up @@ -75,38 +76,61 @@ func (client *Client) Logout() error {

// LoggedIn returns whether the client has authentication data available.
func (client *Client) LoggedIn() bool {
return osutil.FileExists(storeAuthDataFilename())
return osutil.FileExists(storeAuthDataFilename(""))
}

func storeAuthDataFilename() string {
authFilename := os.Getenv("SNAPPY_STORE_AUTH_DATA_FILENAME")
if authFilename != "" {
return authFilename
const authFileEnvKey = "SNAPPY_STORE_AUTH_DATA_FILENAME"

func storeAuthDataFilename(homeDir string) string {
if fn := os.Getenv(authFileEnvKey); fn != "" {
return fn
}
homeDir, err := osutil.CurrentHomeDir()
if err != nil {
panic(err)

if homeDir == "" {
real, err := osutil.RealUser()
if err != nil {
panic(err)
}
homeDir = real.HomeDir
}

return filepath.Join(homeDir, ".snap", "auth.json")
}

// writeAuthData saves authentication details for later reuse through ReadAuthData
func writeAuthData(user User) error {
targetFile := storeAuthDataFilename()
if err := os.MkdirAll(filepath.Dir(targetFile), 0700); err != nil {
real, err := osutil.RealUser()
if err != nil {
return err
}

uid, err := strconv.Atoi(real.Uid)
if err != nil {
return err
}

gid, err := strconv.Atoi(real.Gid)
if err != nil {
return err
}

targetFile := storeAuthDataFilename(real.HomeDir)

if err := osutil.MkdirAllChown(filepath.Dir(targetFile), 0700, uid, gid); err != nil {
return err
}

outStr, err := json.Marshal(user)
if err != nil {
return nil
}

return osutil.AtomicWriteFile(targetFile, []byte(outStr), 0600, 0)
return osutil.AtomicWriteFileChown(targetFile, []byte(outStr), 0600, 0, uid, gid)
}

// readAuthData reads previously written authentication details
func readAuthData() (*User, error) {
sourceFile := storeAuthDataFilename()
sourceFile := storeAuthDataFilename("")
f, err := os.Open(sourceFile)
if err != nil {
return nil, err
Expand All @@ -124,6 +148,6 @@ func readAuthData() (*User, error) {

// removeAuthData removes any previously written authentication details.
func removeAuthData() error {
filename := storeAuthDataFilename()
filename := storeAuthDataFilename("")
return os.Remove(filename)
}
80 changes: 23 additions & 57 deletions client/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ func (cs *clientSuite) TestClientLogin(c *check.C) {
"macaroon": "the-root-macaroon",
"discharges": ["discharge-macaroon"]}}`

home := os.Getenv("HOME")
tmpdir := c.MkDir()
os.Setenv("HOME", tmpdir)
defer os.Setenv("HOME", home)
outfile := filepath.Join(c.MkDir(), "json")
os.Setenv(client.TestAuthFileEnvKey, outfile)
defer os.Unsetenv(client.TestAuthFileEnvKey)

c.Assert(cs.cli.LoggedIn(), check.Equals, false)

Expand All @@ -52,9 +51,8 @@ func (cs *clientSuite) TestClientLogin(c *check.C) {

c.Assert(cs.cli.LoggedIn(), check.Equals, true)

outFile := filepath.Join(tmpdir, ".snap", "auth.json")
c.Check(osutil.FileExists(outFile), check.Equals, true)
content, err := ioutil.ReadFile(outFile)
c.Check(osutil.FileExists(outfile), check.Equals, true)
content, err := ioutil.ReadFile(outfile)
c.Check(err, check.IsNil)
c.Check(string(content), check.Equals, `{"macaroon":"the-root-macaroon","discharges":["discharge-macaroon"]}`)
}
Expand All @@ -67,46 +65,40 @@ func (cs *clientSuite) TestClientLoginError(c *check.C) {
"type": "error"
}`

home := os.Getenv("HOME")
tmpdir := c.MkDir()
os.Setenv("HOME", tmpdir)
defer os.Setenv("HOME", home)
outfile := filepath.Join(c.MkDir(), "json")
os.Setenv(client.TestAuthFileEnvKey, outfile)
defer os.Unsetenv(client.TestAuthFileEnvKey)

user, err := cs.cli.Login("username", "pass", "")

c.Check(user, check.IsNil)
c.Check(err, check.NotNil)

outFile := filepath.Join(tmpdir, ".snap", "auth.json")
c.Check(osutil.FileExists(outFile), check.Equals, false)
c.Check(osutil.FileExists(outfile), check.Equals, false)
}

func (cs *clientSuite) TestClientLogout(c *check.C) {
cs.rsp = `{"type": "sync", "result": {}}`

home := os.Getenv("HOME")
tmpdir := c.MkDir()
os.Setenv("HOME", tmpdir)
defer os.Setenv("HOME", home)
outfile := filepath.Join(c.MkDir(), "json")
os.Setenv(client.TestAuthFileEnvKey, outfile)
defer os.Unsetenv(client.TestAuthFileEnvKey)

err := os.Mkdir(filepath.Join(tmpdir, ".snap"), 0700)
c.Assert(err, check.IsNil)
authPath := filepath.Join(tmpdir, ".snap", "auth.json")
err = ioutil.WriteFile(authPath, []byte(`{"macaroon":"macaroon","discharges":["discharged"]}`), 0600)
err := ioutil.WriteFile(outfile, []byte(`{"macaroon":"macaroon","discharges":["discharged"]}`), 0600)
c.Assert(err, check.IsNil)

err = cs.cli.Logout()
c.Assert(err, check.IsNil)
c.Check(cs.req.Method, check.Equals, "POST")
c.Check(cs.req.URL.Path, check.Equals, fmt.Sprintf("/v2/logout"))

c.Check(osutil.FileExists(authPath), check.Equals, false)
c.Check(osutil.FileExists(outfile), check.Equals, false)
}

func (cs *clientSuite) TestWriteAuthData(c *check.C) {
home := os.Getenv("HOME")
tmpdir := c.MkDir()
os.Setenv("HOME", tmpdir)
defer os.Setenv("HOME", home)
outfile := filepath.Join(c.MkDir(), "json")
os.Setenv(client.TestAuthFileEnvKey, outfile)
defer os.Unsetenv(client.TestAuthFileEnvKey)

authData := client.User{
Macaroon: "macaroon",
Expand All @@ -115,18 +107,16 @@ func (cs *clientSuite) TestWriteAuthData(c *check.C) {
err := client.TestWriteAuth(authData)
c.Assert(err, check.IsNil)

outFile := filepath.Join(tmpdir, ".snap", "auth.json")
c.Check(osutil.FileExists(outFile), check.Equals, true)
content, err := ioutil.ReadFile(outFile)
c.Check(osutil.FileExists(outfile), check.Equals, true)
content, err := ioutil.ReadFile(outfile)
c.Check(err, check.IsNil)
c.Check(string(content), check.Equals, `{"macaroon":"macaroon","discharges":["discharge"]}`)
}

func (cs *clientSuite) TestReadAuthData(c *check.C) {
home := os.Getenv("HOME")
tmpdir := c.MkDir()
os.Setenv("HOME", tmpdir)
defer os.Setenv("HOME", home)
outfile := filepath.Join(c.MkDir(), "json")
os.Setenv(client.TestAuthFileEnvKey, outfile)
defer os.Unsetenv(client.TestAuthFileEnvKey)

authData := client.User{
Macaroon: "macaroon",
Expand All @@ -139,27 +129,3 @@ func (cs *clientSuite) TestReadAuthData(c *check.C) {
c.Assert(err, check.IsNil)
c.Check(readUser, check.DeepEquals, &authData)
}

func (cs *clientSuite) TestStoreAuthDataFilenameDefault(c *check.C) {
home := os.Getenv("HOME")
tmpdir := c.MkDir()
os.Setenv("HOME", tmpdir)
defer os.Setenv("HOME", home)

authFilename := client.TestStoreAuthFilename()

expectedFilename := filepath.Join(tmpdir, ".snap", "auth.json")
c.Check(authFilename, check.Equals, expectedFilename)
}

func (cs *clientSuite) TestStoreAuthDataFilenameViaEnv(c *check.C) {
authFilenameOrig := os.Getenv("SNAPPY_STORE_AUTH_DATA_FILENAME")
tmpdir := c.MkDir()
expectedAuthFilename := filepath.Join(tmpdir, "auth.json")
os.Setenv("SNAPPY_STORE_AUTH_DATA_FILENAME", expectedAuthFilename)
defer os.Setenv("SNAPPY_STORE_AUTH_DATA_FILENAME", authFilenameOrig)

authFilename := client.TestStoreAuthFilename()

c.Check(authFilename, check.Equals, expectedAuthFilename)
}
13 changes: 13 additions & 0 deletions osutil/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package osutil

import (
"errors"
"os"
"path/filepath"

Expand All @@ -40,6 +41,10 @@ const (
// Note that it won't follow symlinks and will replace existing symlinks
// with the real file
func AtomicWriteFile(filename string, data []byte, perm os.FileMode, flags AtomicWriteFlags) (err error) {
return AtomicWriteFileChown(filename, data, perm, flags, -1, -1)
}

func AtomicWriteFileChown(filename string, data []byte, perm os.FileMode, flags AtomicWriteFlags, uid, gid int) (err error) {
if flags&AtomicWriteFollow != 0 {
if fn, err := os.Readlink(filename); err == nil || (fn != "" && os.IsNotExist(err)) {
if filepath.IsAbs(fn) {
Expand Down Expand Up @@ -78,6 +83,14 @@ func AtomicWriteFile(filename string, data []byte, perm os.FileMode, flags Atomi
return err
}

if uid > -1 && gid > -1 {
if err := fd.Chown(uid, gid); err != nil {
return err
}
} else if uid > -1 || gid > -1 {
return errors.New("internal error: AtomicWriteFileChown needs none or both of uid and gid set")
}

if err := fd.Sync(); err != nil {
return err
}
Expand Down
72 changes: 72 additions & 0 deletions osutil/mkdirallchown.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2016 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package osutil

import (
"os"
"path/filepath"
"syscall"
)

// MkdirAllChown is like os.MkdirAll but it calls os.Chown on any
// directories it creates.
func MkdirAllChown(path string, perm os.FileMode, uid, gid int) error {
if s, err := os.Stat(path); err == nil {
if s.IsDir() {
return nil
}

// emulate os.MkdirAll
return &os.PathError{
Op: "mkdir",
Path: path,
Err: syscall.ENOTDIR,
}
}

dir := filepath.Dir(path)
if dir != "/" {
if err := MkdirAllChown(dir, perm, uid, gid); err != nil {
return err
}
}

cand := path + ".mkdir-new"

if err := os.Mkdir(cand, perm); err != nil && !os.IsExist(err) {
return err
}

if err := os.Chown(cand, uid, gid); err != nil {
return err
}

if err := os.Rename(cand, path); err != nil {
return err
}

fd, err := os.Open(dir)
if err != nil {
return err
}
defer fd.Close()

return fd.Sync()
}
Loading

0 comments on commit b507179

Please sign in to comment.