Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.63% covered (success)
98.63%
72 / 73
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
DefaultPresenter
98.63% covered (success)
98.63%
72 / 73
83.33% covered (warning)
83.33%
5 / 6
18
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 present
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
9
 presentInfo
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
4
 presentIPoidInfo
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 presentBlockInfo
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 presentContributionInfo
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
1<?php
2
3namespace MediaWiki\IPInfo\Rest\Presenter;
4
5use MediaWiki\IPInfo\AccessLevelTrait;
6use MediaWiki\IPInfo\Info\BlockInfo;
7use MediaWiki\IPInfo\Info\ContributionInfo;
8use MediaWiki\IPInfo\Info\Info;
9use MediaWiki\IPInfo\Info\IPoidInfo;
10use MediaWiki\IPInfo\Info\Location;
11use MediaWiki\Permissions\PermissionManager;
12use MediaWiki\User\UserIdentity;
13use Wikimedia\Assert\Assert;
14
15class DefaultPresenter {
16    use AccessLevelTrait;
17
18    private PermissionManager $permissionManager;
19
20    public const IPINFO_VIEW_BASIC_RIGHT = 'ipinfo-view-basic';
21    public const IPINFO_VIEW_FULL_RIGHT = 'ipinfo-view-full';
22
23    private const IPINFO_VIEW_BASIC = [
24        'connectionType',
25        'countryNames',
26        'numActiveBlocks',
27        'numLocalEdits',
28        'numRecentEdits',
29        'proxyType',
30        'userType',
31    ];
32
33    private const IPINFO_VIEW_FULL = [
34        'asn',
35        'behaviors',
36        'connectionType',
37        'connectionTypes',
38        'countryNames',
39        'isp',
40        'location',
41        'numActiveBlocks',
42        'numDeletedEdits',
43        'numLocalEdits',
44        'numRecentEdits',
45        'numUsersOnThisIP',
46        'organization',
47        'proxies',
48        'proxyType',
49        'risks',
50        'tunnelOperators',
51        'userType',
52    ];
53
54    /**
55     * The viewing privileges of each level.
56     *
57     * Each describes themselves independently of one another.
58     */
59    private const VIEWING_RIGHTS = [
60        self::IPINFO_VIEW_BASIC_RIGHT => self::IPINFO_VIEW_BASIC,
61        self::IPINFO_VIEW_FULL_RIGHT => self::IPINFO_VIEW_FULL,
62    ];
63
64    /**
65     * @param PermissionManager $permissionManager
66     */
67    public function __construct(
68        PermissionManager $permissionManager
69    ) {
70        $this->permissionManager = $permissionManager;
71    }
72
73    /**
74     * @param array $info The output of MediaWiki\IPInfo\InfoManager::retrieveFromIP()
75     * @param UserIdentity $user User performing the request
76     * @return array
77     */
78    public function present( array $info, UserIdentity $user ): array {
79        Assert::parameterElementType(
80            [ Info::class, IPoidInfo::class, BlockInfo::class, ContributionInfo::class ],
81            $info['data'],
82            "info['data']"
83        );
84
85        $result = [
86            'subject' => $info['subject'],
87            'data' => [],
88        ];
89
90        // Get the highest access list of properties user has permissions for
91        $level = $this->highestAccessLevel( $this->permissionManager->getUserPermissions( $user ) );
92        $viewableProperties = $level ? self::VIEWING_RIGHTS[$level] : [];
93
94        foreach ( $info['data'] as $source => $itemInfo ) {
95            $data = [];
96
97            if ( $itemInfo instanceof Info ) {
98                $data += $this->presentInfo( $itemInfo );
99            } elseif ( $itemInfo instanceof IPoidInfo ) {
100                $data += $this->presentIPoidInfo( $itemInfo );
101            } elseif ( $itemInfo instanceof BlockInfo ) {
102                $data += $this->presentBlockInfo( $itemInfo );
103            } elseif ( $itemInfo instanceof ContributionInfo ) {
104                $data += $this->presentContributionInfo( $itemInfo, $user );
105            }
106
107            // Unset all properties the user doesn't have access to before writing to $result
108            foreach ( $data as $datum => $value ) {
109                if ( !in_array( $datum, $viewableProperties ) ) {
110                    unset( $data[$datum] );
111                }
112            }
113
114            $result['data'][$source] = $data;
115        }
116
117        return $result;
118    }
119
120    /**
121     * Converts an instance of `\MediaWiki\IPInfo\Info\Info` to an array.
122     *
123     * @param Info $info
124     * @return array<string,mixed>
125     */
126    private function presentInfo( Info $info ): array {
127        $coordinates = $info->getCoordinates();
128        $proxyType = $info->getProxyType();
129        $location = $info->getLocation();
130
131        return [
132            'coordinates' => $coordinates ? [
133                'latitude' => $coordinates->getLatitude(),
134                'longitude' => $coordinates->getLongitude(),
135            ] : null,
136            'asn' => $info->getAsn(),
137            'organization' => $info->getOrganization(),
138            'countryNames' => $info->getCountryNames(),
139            'location' => $location ? array_map( static function ( Location $location ) {
140                return [
141                    'id' => $location->getId(),
142                    'label' => $location->getLabel(),
143                ];
144            }, $location ) : null,
145            'isp' => $info->getIsp(),
146            'connectionType' => $info->getConnectionType(),
147            'userType' => $info->getUserType(),
148            'proxyType' => $proxyType ? [
149                'isAnonymousVpn' => $proxyType->isAnonymousVpn(),
150                'isPublicProxy' => $proxyType->isPublicProxy(),
151                'isResidentialProxy' => $proxyType->isResidentialProxy(),
152                'isLegitimateProxy' => $proxyType->isLegitimateProxy(),
153                'isTorExitNode' => $proxyType->isTorExitNode(),
154                'isHostingProvider' => $proxyType->isHostingProvider(),
155
156            ] : null,
157        ];
158    }
159
160    /**
161     * @param IPoidInfo $info
162     * @return array<string,mixed>
163     */
164    private function presentIPoidInfo( IPoidInfo $info ): array {
165        return [
166            'behaviors' => $info->getBehaviors(),
167            'risks' => $info->getRisks(),
168            'connectionTypes' => $info->getConnectionTypes(),
169            'tunnelOperators' => $info->getTunnelOperators(),
170            'proxies' => $info->getProxies(),
171            'numUsersOnThisIP' => $info->getNumUsersOnThisIP(),
172        ];
173    }
174
175    /**
176     * @param BlockInfo $info
177     * @return array<string,int>
178     */
179    private function presentBlockInfo( BlockInfo $info ): array {
180        return [
181            'numActiveBlocks' => $info->getNumActiveBlocks(),
182        ];
183    }
184
185    /**
186     * @param ContributionInfo $info
187     * @param UserIdentity $user
188     * @return array<string,int>
189     */
190    private function presentContributionInfo( ContributionInfo $info, UserIdentity $user ): array {
191        $contributionInfo = [
192            'numLocalEdits' => $info->getNumLocalEdits(),
193            'numRecentEdits' => $info->getNumRecentEdits(),
194        ];
195        if ( $this->permissionManager->userHasRight(
196             $user, 'deletedhistory' ) ) {
197            $contributionInfo['numDeletedEdits'] = $info->getNumDeletedEdits();
198        }
199        return $contributionInfo;
200    }
201}