/*
Copyright 2018 The Kubernetes Authors.

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 event

import (
	"fmt"
	"net/url"

	authnv1 "k8s.io/api/authentication/v1"
	"k8s.io/apiserver/pkg/apis/audit"
	authuser "k8s.io/apiserver/pkg/authentication/user"
	"k8s.io/apiserver/pkg/authorization/authorizer"
)

var _ authorizer.Attributes = &attributes{}

// attributes implements the authorizer attributes interface
// with event data. This is used for enforced audit backends
type attributes struct {
	event *audit.Event
	path  string
}

// NewAttributes returns a new attributes struct and parsed request uri
// if needed
func NewAttributes(event *audit.Event) (authorizer.Attributes, error) {
	a := attributes{
		event: event,
	}
	if event.ObjectRef == nil {
		u, err := url.ParseRequestURI(a.event.RequestURI)
		if err != nil {
			return nil, fmt.Errorf("could not parse url: %v", err)
		}
		a.path = u.Path
	}
	return &a, nil
}

// GetUser returns the user. This is only used for checking audit policy,
// and the audit policy user check is based off the original user,
// not the impersonated user.
func (a *attributes) GetUser() authuser.Info {
	return user(a.event.User)
}

// GetVerb returns the verb
func (a *attributes) GetVerb() string {
	return a.event.Verb
}

// IsReadOnly determines if the verb is a read only action
func (a *attributes) IsReadOnly() bool {
	return a.event.Verb == "get" || a.event.Verb == "list" || a.event.Verb == "watch"
}

// GetNamespace returns the object namespace if present
func (a *attributes) GetNamespace() string {
	if a.event.ObjectRef == nil {
		return ""
	}
	return a.event.ObjectRef.Namespace
}

// GetResource returns the object resource if present
func (a *attributes) GetResource() string {
	if a.event.ObjectRef == nil {
		return ""
	}
	return a.event.ObjectRef.Resource
}

// GetSubresource returns the object subresource if present
func (a *attributes) GetSubresource() string {
	if a.event.ObjectRef == nil {
		return ""
	}
	return a.event.ObjectRef.Subresource
}

// GetName returns the object name if present
func (a *attributes) GetName() string {
	if a.event.ObjectRef == nil {
		return ""
	}
	return a.event.ObjectRef.Name
}

// GetAPIGroup returns the object api group if present
func (a *attributes) GetAPIGroup() string {
	if a.event.ObjectRef == nil {
		return ""
	}
	return a.event.ObjectRef.APIGroup
}

// GetAPIVersion returns the object api version if present
func (a *attributes) GetAPIVersion() string {
	if a.event.ObjectRef == nil {
		return ""
	}
	return a.event.ObjectRef.APIVersion
}

// IsResourceRequest determines if the request was acted on a resource
func (a *attributes) IsResourceRequest() bool {
	return a.event.ObjectRef != nil
}

// GetPath returns the path uri accessed
func (a *attributes) GetPath() string {
	return a.path
}

// user represents the event user
type user authnv1.UserInfo

// GetName returns the user name
func (u user) GetName() string { return u.Username }

// GetUID returns the user uid
func (u user) GetUID() string { return u.UID }

// GetGroups returns the user groups
func (u user) GetGroups() []string { return u.Groups }

// GetExtra returns the user extra data
func (u user) GetExtra() map[string][]string {
	m := map[string][]string{}
	for k, v := range u.Extra {
		m[k] = []string(v)
	}
	return m
}
