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

UpdateGroup::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 0
cts 16
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 13
nc 1
nop 0
crap 2
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 Symfony\Component\Console\Question\ConfirmationQuestion;
33
use \OCA\User_LDAP\Helper;
34
use \OCA\User_LDAP\LDAP;
35
use \OCA\User_LDAP\Group_Proxy;
36
use \OCA\User_LDAP\Mapping\GroupMapping;
37
use \OCP\IDBConnection;
38
use OCP\DB\QueryBuilder\IQueryBuilder;
39
40
class UpdateGroup extends Command {
41
42
	const ERROR_CODE_MISSING_CONF = 1;
43
	const ERROR_CODE_MISSING_MAPPING = 2;
44
45
	public function __construct(LDAP $ldap, Helper $helper, IDBConnection $connection) {
46
		$this->connection = $connection;
47
		$this->ldap = $ldap;
48
		$this->helper = $helper;
49
		parent::__construct();
50
	}
51
52
	protected function configure() {
53
		$this
54
			->setName('ldap:update-group')
55
			->setDescription('update the specified group membership information stored locally')
56
			->addArgument(
57
					'groupID',
58
					InputArgument::REQUIRED | InputArgument::IS_ARRAY,
59
					'the group ID'
60
				)
61
			->addOption(
62
					'assume-yes',
63
					'y',
64
					InputOption::VALUE_NONE,
65
					'Group membership attribute is critical for this command to work properly. You\'ll be asked for verifying the attribute unless you pass this option'
66
				);
67
	}
68
69
	protected function execute(InputInterface $input, OutputInterface $output) {
70
		$groupIDs = $input->getArgument('groupID');
71
		// make sure we don't have duplicated groups in the parameters
72
		$groupIDs = array_unique($groupIDs);
73
74
		if (!$input->getOption('assume-yes')) {
75
			$question = new ConfirmationQuestion("Group membership attribute is critical for this command, please verify.\nType \"y\" if you want to continue or \"n\" to abort: ");
76
			if (!$this->getHelper('question')->ask($input, $output, $question)) {
77
				return;
78
			}
79
		}
80
81
		$helper = $this->helper;
82
		$availableConfigs = $helper->getServerConfigurationPrefixes();
83
84
		// show configuration information, useful to debug
85
		foreach ($availableConfigs as $aconfig) {
86
			$config = new \OCA\User_LDAP\Configuration($aconfig);
87
			$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...
88
			$output->writeln($message, OutputInterface::VERBOSITY_VERBOSE);
89
		}
90
91
		if (empty($availableConfigs)) {
92
			$output->writeln('<error>No active configurations available</error>');
93
			return self::ERROR_CODE_MISSING_CONF;
94
		}
95
96
		if (!$this->checkGroupMappingExists($groupIDs)) {
97
			$output->writeln("<error>Some of the groups are unknown</error>");
98
			return self::ERROR_CODE_MISSING_MAPPING;
99
		}
100
101
		$groupProxy = new Group_Proxy($availableConfigs, $this->ldap);
102
103
		foreach ($groupIDs as $groupID) {
104
			$output->writeln("checking group \"$groupID\"...");
105
			if (!$groupProxy->groupExists($groupID)) {
106
				$output->writeln("\"$groupID\" doesn't exist in LDAP any more, removing local mapping");
107
				$this->removeGroupMapping($groupID);
108
			} else {
109
				$output->writeln("updating \"$groupID\" group merbership information locally", OutputInterface::VERBOSITY_VERBOSE);
110
				$userList = $groupProxy->usersInGroup($groupID);
111
				$userChanges = $this->updateGroupMapping($groupID, $userList);
112
113
				$output->writeln("triggering hooks", OutputInterface::VERBOSITY_VERBOSE);
114
				$output->writeln("new users:");
115
				foreach ($userChanges['added'] as $addedUser) {
116
					$output->writeln($addedUser);
117
					\OCP\Util::emitHook('OC_User', 'post_addToGroup', array('uid' => $addedUser, 'gid' => $groupID));
118
				}
119
				$output->writeln("removed users:");
120
				foreach ($userChanges['removed'] as $removedUser) {
121
					$output->writeln($removedUser);
122
					\OCP\Util::emitHook('OC_User', 'post_removeFromGroup', array('uid' => $removedUser, 'gid' => $groupID));
123
				}
124
			}
125
		}
126
	}
127
128
129
	private function removeGroupMapping($groupName) {
130
		$this->connection->beginTransaction();
131
132
		try {
133
			$query = $this->connection->getQueryBuilder();
134
			$query->delete('ldap_group_mapping', 'lgm')
135
				->where($query->expr()->eq('lgm.owncloud_name', $query->createParameter('group')))
136
				->setParameter('group', $groupName)
137
				->execute();
138
139
			$query2 = $this->connection->getQueryBuilder();
140
			$query2->delete('ldap_group_members', 'lgm')
141
				->where($query->expr()->eq('lgm.owncloudname', $query->createParameter('group')))
142
				->setParameter('group', $groupName)
143
				->execute();
144
145
			$this->connection->commit();
146
		} catch (\Exception $e) {
147
			// Rollback and rethrow the exception
148
			$this->connection->rollback();
149
			throw $e;
150
		}
151
	}
152
153
	/**
154
	 * Return and array with 2 lists: one for the users added and another for the users removed from
155
	 * the group:
156
	 * ['added' => ['user1', 'user20'], 'removed' => ['user22']]
157
	 *
158
	 * @param string $groupName name of the group to be checked
159
	 * @param array $userList array of user names to be compared. For example: ['user1', 'user44'].
160
	 * The list will usually come from the LDAP server and will be compared against the information
161
	 * in the DB.
162
	 */
163
	private function updateGroupMapping($groupName, $userList) {
164
		$query = $this->connection->getQueryBuilder();
165
		$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...
166
		$result = $query->select('*')
167
			->from('ldap_group_members')
168
			->where($query->expr()->eq('owncloudname', $query->createParameter('group')))
169
			->setParameter('group', $groupName)
170
			->execute();
171
		$row = $result->fetch();
172
		if ($row) {
173
			$needToInsert = false;
174
			$mappedList = unserialize($row['owncloudusers']);
175
		} else {
176
			$needToInsert = true;
177
		}
178
		if ($needToInsert) {
179
			$query2 = $this->connection->getQueryBuilder();
180
			$query2->insert('ldap_group_members')
181
				->setValue('owncloudname', $query2->createParameter('group'))
182
				->setValue('owncloudusers', $query2->createParameter('users'))
183
				->setParameter('group', $groupName)
184
				->setParameter('users', serialize($userList))
185
				->execute();
186
			return array('added' => $userList, 'removed' => array());
187
		} else {
188
			$query2 = $this->connection->getQueryBuilder();
189
			$query2->update('ldap_group_members')
190
				->set('owncloudusers', $query2->createParameter('users'))
191
				->where($query2->expr()->eq('owncloudname', $query2->createParameter('group')))
192
				->setParameter('group', $groupName)
193
				->setParameter('users', serialize($userList))
194
				->execute();
195
			// calculate changes
196
			$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...
197
			$usersRemoved = array_diff($mappedList, $userList);
198
			return array('added' => $usersAdded, 'removed' => $usersRemoved);
199
		}
200
	}
201
202
	/**
203
	 * Make sure $groupNames doesn't contain duplicated values. This function could behave
204
	 * unexpectedly otherwise.
205
	 *
206
	 * Take advantage of the owncloud_name column in the DB has a unique constraint.
207
	 *
208
	 * @return true if the count($groupNames) matches the number of
209
	 */
210
	private function checkGroupMappingExists($groupNames) {
211
		$query = $this->connection->getQueryBuilder();
212
		$query->select($query->createFunction('count(owncloud_name) as ngroups'))
213
			->from('ldap_group_mapping')
214
			->where($query->expr()->in('owncloud_name', $query->createParameter('groups')))
215
			->setParameter('groups', $groupNames, IQueryBuilder::PARAM_STR_ARRAY);
216
		$result = $query->execute();
217
		$row = $result->fetch();
218
219
		$countValue = intval($row['ngroups']);
220
		$result->closeCursor();
221
		$requestedGroupNameCount = count($groupNames);
222
		return $countValue === $requestedGroupNameCount;
223
	}
224
}
225