/*
 * Minio Cloud Storage, (C) 2016 Minio, Inc.
 *
 * 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
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

package cmd

import (
	"path"
	"sync"
	"time"
)

// Login handler implements JWT login token generator, which upon login request
// along with username and password is generated.
func (br *browserPeerAPIHandlers) LoginHandler(args *RPCLoginArgs, reply *RPCLoginReply) error {
	jwt, err := newJWT(defaultInterNodeJWTExpiry)
	if err != nil {
		return err
	}
	if err = jwt.Authenticate(args.Username, args.Password); err != nil {
		return err
	}
	token, err := jwt.GenerateToken(args.Username)
	if err != nil {
		return err
	}
	reply.Token = token
	reply.ServerVersion = Version
	reply.Timestamp = time.Now().UTC()
	return nil
}

// SetAuthPeerArgs - Arguments collection for SetAuth RPC call
type SetAuthPeerArgs struct {
	// For Auth
	GenericArgs

	// New credentials that receiving peer should update to.
	Creds credential
}

// SetAuthPeer - Update to new credentials sent from a peer Minio
// server. Since credentials are already validated on the sending
// peer, here we just persist to file and update in-memory config. All
// subsequently running isRPCTokenValid() calls will fail, and clients
// will be forced to re-establish connections. Connections will be
// re-established only when the sending client has also updated its
// credentials.
func (br *browserPeerAPIHandlers) SetAuthPeer(args SetAuthPeerArgs, reply *GenericReply) error {
	// Check auth
	if !isRPCTokenValid(args.Token) {
		return errInvalidToken
	}

	// Update credentials in memory
	serverConfig.SetCredential(args.Creds)

	// Save credentials to config file
	if err := serverConfig.Save(); err != nil {
		errorIf(err, "Error updating config file with new credentials sent from browser RPC.")
		return err
	}

	return nil
}

// Sends SetAuthPeer RPCs to all peers in the Minio cluster
func updateCredsOnPeers(creds credential) map[string]error {
	// Get list of peer addresses (from globalS3Peers)
	peers := []string{}
	for _, p := range globalS3Peers {
		peers = append(peers, p.addr)
	}

	// Array of errors for each peer
	errs := make([]error, len(peers))
	var wg sync.WaitGroup

	// Launch go routines to send request to each peer in parallel.
	for ix := range peers {
		wg.Add(1)
		go func(ix int) {
			defer wg.Done()

			// Exclude self to avoid race with
			// invalidating the RPC token.
			if peers[ix] == globalMinioAddr {
				errs[ix] = nil
				return
			}

			// Initialize client
			client := newAuthClient(&authConfig{
				accessKey:   serverConfig.GetCredential().AccessKeyID,
				secretKey:   serverConfig.GetCredential().SecretAccessKey,
				address:     peers[ix],
				secureConn:  isSSL(),
				path:        path.Join(reservedBucket, browserPeerPath),
				loginMethod: "Browser.LoginHandler",
			})

			// Construct RPC call arguments.
			args := SetAuthPeerArgs{Creds: creds}

			// Make RPC call - we only care about error
			// response and not the reply.
			err := client.Call("Browser.SetAuthPeer", &args, &GenericReply{})

			// We try a bit hard (3 attempts with 1 second delay)
			// to set creds on peers in case of failure.
			if err != nil {
				for i := 0; i < 2; i++ {
					time.Sleep(1 * time.Second) // 1 second delay.
					err = client.Call("Browser.SetAuthPeer", &args, &GenericReply{})
					if err == nil {
						break
					}
				}
			}

			// Send result down the channel
			errs[ix] = err
		}(ix)
	}

	// Wait for requests to complete.
	wg.Wait()

	// Put errors into map.
	errsMap := make(map[string]error)
	for i, err := range errs {
		if err != nil {
			errsMap[peers[i]] = err
		}
	}

	return errsMap
}