Completed
Pull Request — master (#14)
by Juan Pablo
06:38
created

UpdateGroup   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 182
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 17
lcom 1
cbo 2
dl 0
loc 182
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A configure() 0 16 1
B execute() 0 55 9
A removeGroupMapping() 0 23 2
B updateGroupMapping() 0 38 3
A checkGroupMappingExists() 0 14 1
1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Joas Schilling <[email protected]>
5
 * @author Morris Jobke <[email protected]>
6
 * @author Thomas Müller <[email protected]>
7
 *
8
 * @copyright Copyright (c) 2016, ownCloud GmbH.
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
22
 *
23
 */
24
25
namespace OCA\User_LDAP\Command;
26
27
use Symfony\Component\Console\Command\Command;
28
use Symfony\Component\Console\Input\InputArgument;
29
use Symfony\Component\Console\Input\InputOption;
30
use Symfony\Component\Console\Input\InputInterface;
31
use Symfony\Component\Console\Output\OutputInterface;
32
use \OCA\User_LDAP\Helper;
33
use \OCA\User_LDAP\LDAP;
34
use \OCA\User_LDAP\Group_Proxy;
35
use \OCA\User_LDAP\Mapping\GroupMapping;
36
use \OCP\IDBConnection;
37
use OCP\DB\QueryBuilder\IQueryBuilder;
38
39
class UpdateGroup extends Command {
40
41
	const ERROR_CODE_MISSING_CONF = 1;
42
	const ERROR_CODE_MISSING_MAPPING = 2;
43
44
	public function __construct(LDAP $ldap, Helper $helper, IDBConnection $connection) {
45
		$this->connection = $connection;
46
		$this->ldap = $ldap;
47
		$this->helper = $helper;
48
		parent::__construct();
49
	}
50
51
	protected function configure() {
52
		$this
53
			->setName('ldap:update-group')
54
			->setDescription('update the specified group membership information stored locally')
55
			->addArgument(
56
					'groupID',
57
					InputArgument::REQUIRED | InputArgument::IS_ARRAY,
58
					'the group ID'
59
				)
60
			->addOption(
61
					'hide-warning',
62
					null,
63
					InputOption::VALUE_NONE,
64
					'Group membership attribute is critical for this command to work properly. This option hides the warning and the additional output associated with it.'
65
				);
66
	}
67
68
	protected function execute(InputInterface $input, OutputInterface $output) {
69
		$groupIDs = $input->getArgument('groupID');
70
		// make sure we don't have duplicated groups in the parameters
71
		$groupIDs = array_unique($groupIDs);
72
73
		$helper = $this->helper;
74
		$availableConfigs = $helper->getServerConfigurationPrefixes();
75
76
		if (!$input->getOption('hide-warning')) {
77
			$output->writeln("Group membership attribute is critical for this command, please verify.");
78
			// show configuration information, useful to debug
79
			foreach ($availableConfigs as $aconfig) {
80
				$config = new \OCA\User_LDAP\Configuration($aconfig);
81
				$message = '* ' . $config->ldapHost . ':' . $config->ldapPort . ' -> ' . $config->ldapGroupMemberAssocAttr;
0 ignored issues
show
Documentation introduced by
The property ldapHost does not exist on object<OCA\User_LDAP\Configuration>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property ldapPort does not exist on object<OCA\User_LDAP\Configuration>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property ldapGroupMemberAssocAttr does not exist on object<OCA\User_LDAP\Configuration>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
82
				$output->writeln($message);
83
			}
84
		}
85
86
87
		if (empty($availableConfigs)) {
88
			$output->writeln('<error>No active configurations available</error>');
89
			return self::ERROR_CODE_MISSING_CONF;
90
		}
91
92
		if (!$this->checkGroupMappingExists($groupIDs)) {
93
			$output->writeln("<error>Some of the groups are unknown</error>");
94
			return self::ERROR_CODE_MISSING_MAPPING;
95
		}
96
97
		$groupProxy = new Group_Proxy($availableConfigs, $this->ldap);
98
99
		foreach ($groupIDs as $groupID) {
100
			$output->writeln("checking group \"$groupID\"...");
101
			if (!$groupProxy->groupExists($groupID)) {
102
				$output->writeln("\"$groupID\" doesn't exist in LDAP any more, removing local mapping");
103
				$this->removeGroupMapping($groupID);
104
			} else {
105
				$output->writeln("updating \"$groupID\" group membership information locally", OutputInterface::VERBOSITY_VERBOSE);
106
				$userList = $groupProxy->usersInGroup($groupID);
107
				$userChanges = $this->updateGroupMapping($groupID, $userList);
108
109
				$output->writeln("triggering hooks", OutputInterface::VERBOSITY_VERBOSE);
110
				$output->writeln("new users:");
111
				foreach ($userChanges['added'] as $addedUser) {
112
					$output->writeln($addedUser);
113
					\OCP\Util::emitHook('OC_User', 'post_addToGroup', array('uid' => $addedUser, 'gid' => $groupID));
114
				}
115
				$output->writeln("removed users:");
116
				foreach ($userChanges['removed'] as $removedUser) {
117
					$output->writeln($removedUser);
118
					\OCP\Util::emitHook('OC_User', 'post_removeFromGroup', array('uid' => $removedUser, 'gid' => $groupID));
119
				}
120
			}
121
		}
122
	}
123
124
125
	private function removeGroupMapping($groupName) {
126
		$this->connection->beginTransaction();
127
128
		try {
129
			$query = $this->connection->getQueryBuilder();
130
			$query->delete('ldap_group_mapping')
131
				->where($query->expr()->eq('owncloud_name', $query->createParameter('group')))
132
				->setParameter('group', $groupName)
133
				->execute();
134
135
			$query2 = $this->connection->getQueryBuilder();
136
			$query2->delete('ldap_group_members')
137
				->where($query->expr()->eq('owncloudname', $query->createParameter('group')))
138
				->setParameter('group', $groupName)
139
				->execute();
140
141
			$this->connection->commit();
142
		} catch (\Exception $e) {
143
			// Rollback and rethrow the exception
144
			$this->connection->rollback();
145
			throw $e;
146
		}
147
	}
148
149
	/**
150
	 * Return and array with 2 lists: one for the users added and another for the users removed from
151
	 * the group:
152
	 * ['added' => ['user1', 'user20'], 'removed' => ['user22']]
153
	 *
154
	 * @param string $groupName name of the group to be checked
155
	 * @param array $userList array of user names to be compared. For example: ['user1', 'user44'].
156
	 * The list will usually come from the LDAP server and will be compared against the information
157
	 * in the DB.
158
	 */
159
	private function updateGroupMapping($groupName, $userList) {
160
		$query = $this->connection->getQueryBuilder();
161
		$needToInsert = false;
0 ignored issues
show
Unused Code introduced by
$needToInsert is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
162
		$result = $query->select('*')
163
			->from('ldap_group_members')
164
			->where($query->expr()->eq('owncloudname', $query->createParameter('group')))
165
			->setParameter('group', $groupName)
166
			->execute();
167
		$row = $result->fetch();
168
		if ($row) {
169
			$needToInsert = false;
170
			$mappedList = unserialize($row['owncloudusers']);
171
		} else {
172
			$needToInsert = true;
173
		}
174
		if ($needToInsert) {
175
			$query2 = $this->connection->getQueryBuilder();
176
			$query2->insert('ldap_group_members')
177
				->setValue('owncloudname', $query2->createParameter('group'))
178
				->setValue('owncloudusers', $query2->createParameter('users'))
179
				->setParameter('group', $groupName)
180
				->setParameter('users', serialize($userList))
181
				->execute();
182
			return array('added' => $userList, 'removed' => array());
183
		} else {
184
			$query2 = $this->connection->getQueryBuilder();
185
			$query2->update('ldap_group_members')
186
				->set('owncloudusers', $query2->createParameter('users'))
187
				->where($query2->expr()->eq('owncloudname', $query2->createParameter('group')))
188
				->setParameter('group', $groupName)
189
				->setParameter('users', serialize($userList))
190
				->execute();
191
			// calculate changes
192
			$usersAdded = array_diff($userList, $mappedList);
0 ignored issues
show
Bug introduced by
The variable $mappedList does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
193
			$usersRemoved = array_diff($mappedList, $userList);
194
			return array('added' => $usersAdded, 'removed' => $usersRemoved);
195
		}
196
	}
197
198
	/**
199
	 * Make sure $groupNames doesn't contain duplicated values. This function could behave
200
	 * unexpectedly otherwise.
201
	 *
202
	 * Take advantage of the owncloud_name column in the DB has a unique constraint.
203
	 *
204
	 * @return true if the count($groupNames) matches the number of
205
	 */
206
	private function checkGroupMappingExists($groupNames) {
207
		$query = $this->connection->getQueryBuilder();
208
		$query->select($query->createFunction('count(owncloud_name) as ngroups'))
209
			->from('ldap_group_mapping')
210
			->where($query->expr()->in('owncloud_name', $query->createParameter('groups')))
211
			->setParameter('groups', $groupNames, IQueryBuilder::PARAM_STR_ARRAY);
212
		$result = $query->execute();
213
		$row = $result->fetch();
214
215
		$countValue = intval($row['ngroups']);
216
		$result->closeCursor();
217
		$requestedGroupNameCount = count($groupNames);
218
		return $countValue === $requestedGroupNameCount;
219
	}
220
}
221