Completed
Push — master ( 59d827...fe270b )
by leo
05:52
created

ValidateController   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 92.13%

Importance

Changes 0
Metric Value
dl 0
loc 212
ccs 82
cts 89
cp 0.9213
rs 10
c 0
b 0
f 0
wmc 30
lcom 1
cbo 7

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B v1ValidateAction() 0 23 6
A v2ValidateAction() 0 4 1
A v3ValidateAction() 0 4 1
D casValidate() 0 40 9
B successResponse() 0 31 5
A failureResponse() 0 21 2
A lockTicket() 0 4 1
A unlockTicket() 0 4 1
A removeXmlFirstLine() 0 9 2
A returnXML() 0 4 1
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: chenyihong
5
 * Date: 16/8/1
6
 * Time: 14:52
7
 */
8
9
namespace Leo108\CAS\Http\Controllers;
10
11
use Illuminate\Support\Str;
12
use Leo108\CAS\Contracts\TicketLocker;
13
use Leo108\CAS\Repositories\TicketRepository;
14
use Leo108\CAS\Exceptions\CAS\CasException;
15
use Leo108\CAS\Models\Ticket;
16
use Illuminate\Http\Request;
17
use Illuminate\Http\Response;
18
use SimpleXMLElement;
19
20
class ValidateController extends Controller
21
{
22
    /**
23
     * @var TicketLocker
24
     */
25
    protected $ticketLocker;
26
    /**
27
     * @var TicketRepository
28
     */
29
    protected $ticketRepository;
30
31
    const BASE_XML = '<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas"></cas:serviceResponse>';
32
33
    /**
34
     * ValidateController constructor.
35
     * @param TicketLocker     $ticketLocker
36
     * @param TicketRepository $ticketRepository
37
     */
38 4
    public function __construct(TicketLocker $ticketLocker, TicketRepository $ticketRepository)
39
    {
40 4
        $this->ticketLocker     = $ticketLocker;
41 4
        $this->ticketRepository = $ticketRepository;
42 4
    }
43
44 1
    public function v1ValidateAction(Request $request)
45
    {
46 1
        $service = $request->get('service', '');
47 1
        $ticket  = $request->get('ticket', '');
48 1
        if (empty($service) || empty($ticket)) {
49 1
            return new Response('no');
50
        }
51
52 1
        if (!$this->lockTicket($ticket)) {
53 1
            return new Response('no');
54
        }
55 1
        $record = $this->ticketRepository->getByTicket($ticket);
56 1
        if (!$record || $record->service_url != $service) {
57 1
            $this->unlockTicket($ticket);
58
59 1
            return new Response('no');
60
        }
61 1
        $this->ticketRepository->invalidTicket($record);
0 ignored issues
show
Bug introduced by
It seems like $record defined by $this->ticketRepository->getByTicket($ticket) on line 55 can also be of type boolean; however, Leo108\CAS\Repositories\...sitory::invalidTicket() does only seem to accept object<Leo108\CAS\Models\Ticket>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
62
63 1
        $this->unlockTicket($ticket);
64
65 1
        return new Response('yes');
66
    }
67
68 1
    public function v2ValidateAction(Request $request)
69
    {
70 1
        return $this->casValidate($request, false);
71
    }
72
73 1
    public function v3ValidateAction(Request $request)
74
    {
75 1
        return $this->casValidate($request, true);
76
    }
77
78
    /**
79
     * @param Request $request
80
     * @param bool    $returnAttr
81
     * @return Response
82
     */
83 1
    protected function casValidate(Request $request, $returnAttr)
84
    {
85 1
        $service = $request->get('service', '');
86 1
        $ticket  = $request->get('ticket', '');
87 1
        $format  = strtoupper($request->get('format', 'XML'));
88 1
        if (empty($service) || empty($ticket)) {
89 1
            return $this->failureResponse(
90 1
                CasException::INVALID_REQUEST,
91 1
                'param service and ticket can not be empty',
92
                $format
93 1
            );
94
        }
95
96 1
        if (!$this->lockTicket($ticket)) {
97 1
            return $this->failureResponse(CasException::INTERNAL_ERROR, 'try to lock ticket failed', $format);
98
        }
99
100 1
        $record = $this->ticketRepository->getByTicket($ticket);
101
        try {
102 1
            if (!$record) {
103 1
                throw new CasException(CasException::INVALID_TICKET, 'ticket is not valid');
104
            }
105
106 1
            if ($record->service_url != $service) {
107 1
                throw new CasException(CasException::INVALID_SERVICE, 'service is not valid');
108
            }
109 1
        } catch (CasException $e) {
110
            //invalid ticket if error occur
111 1
            $record instanceof Ticket && $this->ticketRepository->invalidTicket($record);
112 1
            $this->unlockTicket($ticket);
113
114 1
            return $this->failureResponse($e->getCasErrorCode(), $e->getMessage(), $format);
115
        }
116 1
        $this->ticketRepository->invalidTicket($record);
0 ignored issues
show
Bug introduced by
It seems like $record defined by $this->ticketRepository->getByTicket($ticket) on line 100 can also be of type boolean; however, Leo108\CAS\Repositories\...sitory::invalidTicket() does only seem to accept object<Leo108\CAS\Models\Ticket>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
117 1
        $this->unlockTicket($ticket);
118
119 1
        $attr = $returnAttr ? $record->user->getCASAttributes() : [];
120
121 1
        return $this->successResponse($record->user->getName(), $attr, $format);
122
    }
123
124
    /**
125
     * @param string $username
126
     * @param array  $attrs
127
     * @param string $format
128
     * @return Response
129
     */
130 1
    protected function successResponse($username, $attrs, $format)
131
    {
132 1
        if (strtoupper($format) === 'JSON') {
133
            $data = [
134
                'serviceResponse' => [
135
                    'authenticationSuccess' => [
136 1
                        'user' => $username,
137 1
                    ],
138 1
                ],
139 1
            ];
140
141 1
            if (!empty($attrs)) {
142 1
                $data['serviceResponse']['authenticationSuccess']['attributes'] = $attrs;
143 1
            }
144
145 1
            return new Response($data);
146
        } else {
147 1
            $xml          = simplexml_load_string(self::BASE_XML);
148 1
            $childSuccess = $xml->addChild('cas:authenticationSuccess');
149 1
            $childSuccess->addChild('cas:user', $username);
150
151 1
            if (!empty($attrs)) {
152 1
                $childAttrs = $childSuccess->addChild('cas:attributes');
153 1
                foreach ($attrs as $key => $value) {
154 1
                    $childAttrs->addChild('cas:'.$key, $value);
155 1
                }
156 1
            }
157
158 1
            return $this->returnXML($xml);
159
        }
160
    }
161
162
    /**
163
     * @param string $code
164
     * @param string $desc
165
     * @param string $format
166
     * @return Response
167
     */
168 1
    protected function failureResponse($code, $desc, $format)
169
    {
170 1
        if (strtoupper($format) === 'JSON') {
171 1
            return new Response(
172
                [
173
                    'serviceResponse' => [
174
                        'authenticationFailure' => [
175 1
                            'code'        => $code,
176 1
                            'description' => $desc,
177 1
                        ],
178 1
                    ],
179
                ]
180 1
            );
181
        } else {
182 1
            $xml          = simplexml_load_string(self::BASE_XML);
183 1
            $childFailure = $xml->addChild('cas:authenticationFailure', $desc);
184 1
            $childFailure->addAttribute('code', $code);
185
186 1
            return $this->returnXML($xml);
187
        }
188
    }
189
190
    /**
191
     * @param string $ticket
192
     * @return bool
193
     */
194 1
    protected function lockTicket($ticket)
195
    {
196 1
        return $this->ticketLocker->acquireLock($ticket, config('cas.lock_timeout'));
197
    }
198
199
    /**
200
     * @param string $ticket
201
     * @return bool
202
     */
203 1
    protected function unlockTicket($ticket)
204
    {
205 1
        return $this->ticketLocker->releaseLock($ticket);
206
    }
207
208
    /**
209
     * remove the first line of xml string
210
     * @param string $str
211
     * @return string
212
     */
213
    protected function removeXmlFirstLine($str)
214
    {
215
        $first = '<?xml version="1.0"?>';
216
        if (Str::startsWith($str, $first)) {
217
            return trim(substr($str, strlen($first)));
218
        }
219
220
        return $str;
221
    }
222
223
    /**
224
     * @param SimpleXMLElement $xml
225
     * @return Response
226
     */
227
    protected function returnXML(SimpleXMLElement $xml)
228
    {
229
        return new Response($this->removeXmlFirstLine($xml->asXML()), 200, array('Content-Type' => 'application/xml'));
0 ignored issues
show
Security Bug introduced by
It seems like $xml->asXML() targeting SimpleXMLElement::asXML() can also be of type false; however, Leo108\CAS\Http\Controll...r::removeXmlFirstLine() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
230
    }
231
}