/*
 * Minio Cloud Storage, (C) 2015 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 main

import (
	"net/http"
	"strings"

	"github.com/minio/minio/pkg/probe"
)

const (
	rpcAuthHeaderPrefix = "MINIORPC"
)

// getRPCCredentialsFromAuth parse credentials tag from authorization value
// Authorization:
//     Authorization: MINIORPC Credential=admin/20130524/milkyway/rpc/rpc_request,
//     SignedHeaders=host;x-minio-date, Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024
func getRPCCredentialsFromAuth(authValue string) ([]string, *probe.Error) {
	if authValue == "" {
		return nil, probe.NewError(errMissingAuthHeaderValue)
	}
	authFields := strings.Split(strings.TrimSpace(authValue), ",")
	if len(authFields) != 3 {
		return nil, probe.NewError(errInvalidAuthHeaderValue)
	}
	authPrefixFields := strings.Fields(authFields[0])
	if len(authPrefixFields) != 2 {
		return nil, probe.NewError(errMissingFieldsAuthHeader)
	}
	if authPrefixFields[0] != rpcAuthHeaderPrefix {
		return nil, probe.NewError(errInvalidAuthHeaderPrefix)
	}
	credentials := strings.Split(strings.TrimSpace(authPrefixFields[1]), "=")
	if len(credentials) != 2 {
		return nil, probe.NewError(errMissingFieldsCredentialTag)
	}
	if len(strings.Split(strings.TrimSpace(authFields[1]), "=")) != 2 {
		return nil, probe.NewError(errMissingFieldsSignedHeadersTag)
	}
	if len(strings.Split(strings.TrimSpace(authFields[2]), "=")) != 2 {
		return nil, probe.NewError(errMissingFieldsSignatureTag)
	}
	credentialElements := strings.Split(strings.TrimSpace(credentials[1]), "/")
	if len(credentialElements) != 5 {
		return nil, probe.NewError(errCredentialTagMalformed)
	}
	return credentialElements, nil
}

// verify if rpcAuthHeader value has valid region
func isValidRPCRegion(authHeaderValue string) *probe.Error {
	credentialElements, err := getRPCCredentialsFromAuth(authHeaderValue)
	if err != nil {
		return err.Trace()
	}
	region := credentialElements[2]
	if region != "milkyway" {
		return probe.NewError(errInvalidRegion)
	}
	return nil
}

// stripRPCAccessKeyID - strip only access key id from auth header
func stripRPCAccessKeyID(authHeaderValue string) (string, *probe.Error) {
	if err := isValidRegion(authHeaderValue); err != nil {
		return "", err.Trace()
	}
	credentialElements, err := getRPCCredentialsFromAuth(authHeaderValue)
	if err != nil {
		return "", err.Trace()
	}
	accessKeyID := credentialElements[0]
	if !IsValidAccessKey(accessKeyID) {
		return "", probe.NewError(errAccessKeyIDInvalid)
	}
	return accessKeyID, nil
}

// initSignatureRPC initializing rpc signature verification
func initSignatureRPC(req *http.Request) (*rpcSignature, *probe.Error) {
	// strip auth from authorization header
	authHeaderValue := req.Header.Get("Authorization")
	accessKeyID, err := stripAccessKeyID(authHeaderValue)
	if err != nil {
		return nil, err.Trace()
	}
	authConfig, err := LoadConfig()
	if err != nil {
		return nil, err.Trace()
	}
	authFields := strings.Split(strings.TrimSpace(authHeaderValue), ",")
	signedHeaders := strings.Split(strings.Split(strings.TrimSpace(authFields[1]), "=")[1], ";")
	signature := strings.Split(strings.TrimSpace(authFields[2]), "=")[1]
	for _, user := range authConfig.Users {
		if user.AccessKeyID == accessKeyID {
			signature := &rpcSignature{
				AccessKeyID:     user.AccessKeyID,
				SecretAccessKey: user.SecretAccessKey,
				Signature:       signature,
				SignedHeaders:   signedHeaders,
				Request:         req,
			}
			return signature, nil
		}
	}
	return nil, probe.NewError(errAccessKeyIDInvalid)
}