// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package ssh

import (
	"context"
	"fmt"
	"net"

	"github.com/hashicorp/vault/sdk/framework"
	"github.com/hashicorp/vault/sdk/logical"
)

func pathLookup(b *backend) *framework.Path {
	return &framework.Path{
		Pattern: "lookup",

		DisplayAttrs: &framework.DisplayAttributes{
			OperationPrefix: operationPrefixSSH,
			OperationVerb:   "list",
			OperationSuffix: "roles-by-ip",
		},

		Fields: map[string]*framework.FieldSchema{
			"ip": {
				Type:        framework.TypeString,
				Description: "[Required] IP address of remote host",
			},
		},

		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.UpdateOperation: b.pathLookupWrite,
		},

		HelpSynopsis:    pathLookupSyn,
		HelpDescription: pathLookupDesc,
	}
}

func (b *backend) pathLookupWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	ipAddr := d.Get("ip").(string)
	if ipAddr == "" {
		return logical.ErrorResponse("Missing ip"), nil
	}
	ip := net.ParseIP(ipAddr)
	if ip == nil {
		return logical.ErrorResponse(fmt.Sprintf("Invalid IP %q", ip.String())), nil
	}

	// Get all the roles created in the backend.
	keys, err := req.Storage.List(ctx, "roles/")
	if err != nil {
		return nil, err
	}

	// Look for roles which has CIDR blocks that encompasses the given IP
	// and create a list out of it.
	var matchingRoles []string
	for _, role := range keys {
		if contains, _ := roleContainsIP(ctx, req.Storage, role, ip.String()); contains {
			matchingRoles = append(matchingRoles, role)
		}
	}

	// Add roles that are allowed to accept any IP address.
	zeroAddressEntry, err := b.getZeroAddressRoles(ctx, req.Storage)
	if err != nil {
		return nil, err
	}
	if zeroAddressEntry != nil {
		matchingRoles = append(matchingRoles, zeroAddressEntry.Roles...)
	}

	// This list may potentially reveal more information than it is supposed to.
	// The roles for which the client is not authorized to will also be displayed.
	// However, if the client tries to use the role for which the client is not
	// authenticated, it will fail. It is not a problem. In a way this can be
	// viewed as a feature. The client can ask for permissions to be given for
	// a specific role if things are not working!
	//
	// Ideally, the role names should be filtered and only the roles which
	// the client is authorized to see, should be returned.
	return &logical.Response{
		Data: map[string]interface{}{
			"roles": matchingRoles,
		},
	}, nil
}

const pathLookupSyn = `
List all the roles associated with the given IP address.
`

const pathLookupDesc = `
The IP address for which the key is requested, is searched in the CIDR blocks
registered with vault using the 'roles' endpoint. Keys can be generated only by
specifying the 'role' name. The roles that can be used to generate the key for
a particular IP, are listed via this endpoint. For example, if this backend is
mounted at "ssh", then "ssh/lookup" lists the roles associated with keys can be
generated for a target IP, if the CIDR block encompassing the IP is registered
with vault.
`
