pakus's avatar
Level 2

How to remove an object from a Collection

Hi folks!

I'm coding an app to maintain which SSH keys can access to a SSH server. The key are grouped in keyGroups and the servers in hostGroups. The rules to allow/deny access are based in keyGroup to hostGroup access.

First question

First, I have a method which based on some Allow/Deny rules provides all the SSH keys that are allowed to a SSH server (host).

    // TODO: Needs a refactor to make code more readable.
    public function getSSHKeysForHost(): Collection
    {      
        $hostGroups = $this->groups;
        foreach ($hostGroups as $hostGroup) {
            $rules = $hostGroup->rules;
            foreach ($rules as $rule) {
                $keygroup = $rule->source;
                $keys = $keyGroup->keys()->where('enabled', true)->get();
                foreach ($keys as $key) {
                    switch ($rule->action) {
                        case ControlRuleAction::Deny:
                            //PROBLEMATIC CODE
							//  It should remove the object inside the collection with the keyName=$key->name
                            $sshKeys->forget($key->name); 
                            break;
                        case ControlRuleAction::Allow:
                            // It should add a new object containing the SSH key.
                            $content = explode(' ', $key->public, 3);
                            $content[2] = 'ssham: ' . $key->name;
                            $sshKeys->push([
                                'keyName' => $key->name,
                                'keyContent' => implode(' ', $content)
                            ]);
                            break;
                        default:
                            // There is no more cases, but just in case (NOOP).
                    }
                }
            }
        }
        return $sshKeys;
    }

In the code above there is a $sshKeys->forget($key->name); which is not working... I would like to remove the object (array entry) containing the specified "$key->name", but I'm missing something there

Second question

I have a lot of concerns about how I'm building this Collection containing all the SSH keys that are allowed to access the server (host)

Can you point me out to a better approach?

0 likes
9 replies
LaryAI's avatar
Level 58

To remove an object from a Collection, you can use the reject method. Here's how you can modify the getSSHKeysForHost method to remove the object inside the collection with the keyName equal to $key->name:

public function getSSHKeysForHost(): Collection
{
    $sshKeys = collect();
    $hostGroups = $this->groups;
    foreach ($hostGroups as $hostGroup) {
        $rules = $hostGroup->rules;
        foreach ($rules as $rule) {
            $keygroup = $rule->source;
            $keys = $keyGroup->keys()->where('enabled', true)->get();
            foreach ($keys as $key) {
                switch ($rule->action) {
                    case ControlRuleAction::Deny:
                        $sshKeys = $sshKeys->reject(function ($sshKey) use ($key) {
                            return $sshKey['keyName'] === $key->name;
                        });
                        break;
                    case ControlRuleAction::Allow:
                        $content = explode(' ', $key->public, 3);
                        $content[2] = 'ssham: ' . $key->name;
                        $sshKeys->push([
                            'keyName' => $key->name,
                            'keyContent' => implode(' ', $content)
                        ]);
                        break;
                    default:
                        // There is no more cases, but just in case (NOOP).
                }
            }
        }
    }
    return $sshKeys;
}

As for a better approach, you could consider using a database to store the SSH keys and their relationships with the host groups. This would allow you to use more efficient queries to retrieve the allowed SSH keys for a given host. You could also consider using a dedicated SSH key management tool instead of building your own.

1 like
pakus's avatar
Level 2

How can I use slice() with the multidimensional array?

$sshKeys = $sshKeys->silce($key->name);
1 like
jaseofspades88's avatar

@Snapey @pakus - this is your answer. I would advise you write a nice clean, collection pipeline that clearly explains what you're trying to do. Your code is just a mess and it's going to take anybody, including you a long time to remember what it's doing when you return to it, six months from now.

collect([1, 2, 3, 4])
    ->filter(fn ($item) => $item > 2)
    ->toArray()
// [3, 4]

This is a simple example of how to filter an array of numbers when the number is greater than 2. Use this as a template and put all that convoluted business logic in and it'll work

1 like
gugun84's avatar

To remove an object from a collection in most programming languages, you can use the following general steps:

Identify the collection that contains the object you want to remove. Identify the specific object within the collection that you want to remove. Use the appropriate method or function provided by the programming language to remove the object from the collection. For example, in Java, you can remove an object from a collection by calling the "remove" method on the collection object and passing in the object you want to remove as an argument:

List<String> list = new ArrayList<String>(); list.add("item1"); list.add("item2"); list.add("item3");

list.remove("item2"); // remove the second item from the list

In Python, you can remove an object from a list by calling the "remove" method on the list object and passing in the object you want to remove as an argument:

my_list = ['item1', 'item2', 'item3'] my_list.remove('item2') # remove the second item from the list

pakus's avatar
Level 2

After some refactors and the help of ChatGPT (I'm impressed) this is the new code:

public function getSSHKeysForHost(): Collection
    {
        $sshKeys = collect();

        // Get the host group IDs for this host.
        $hostGroupIds = $this->groups()->pluck('hostgroups.id');

        // Get the key group IDs for the rules that allow access to the host.
        $keyGroupIds = ControlRule::whereIn('target_id', $hostGroupIds)
            ->select('source_id')
            ->distinct()
            ->pluck('source_id');

        // Get all keys for the key groups that allow access to the host and are enabled.
        $keys = KeyGroup::whereIn('id', $keyGroupIds)
            ->with('keys')
            ->get()
            ->pluck('keys')
            ->flatten()
            ->where('enabled', true);

        // Add the keys to the $sshKeys collection.
        foreach ($keys as $key) {
            $sshKeys->push($key->public);
        }

        return $sshKeys;
    }
1 like
jaseofspades88's avatar
return KeyGroup::whereIn('id', $keyGroupIds)
            ->with('keys')
            ->get()
            ->pluck('keys')
            ->flatten()
            ->where('enabled', true)
            ->map(fn ($key) => $key->public); 
2 likes

Please or to participate in this conversation.