├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── ownercheck
├── __init__.py
├── apps.py
├── conf.py
├── db.py
└── domains.py
├── requirements.txt
├── setup.cfg
├── setup.py
├── tests
├── __init__.py
├── conf.py
├── data
│ ├── google.web.html
│ ├── index.html
│ ├── snapchat.web.html
│ ├── twitter.android.html
│ └── twitter.ios.html
├── test_apps.py
├── test_db.py
└── test_domains.py
└── tox.ini
/.gitignore:
--------------------------------------------------------------------------------
1 | .env*
2 | *.db
3 | *.pyc
4 | *.egg-info*
5 | *.cache/
6 | __pycache__*
7 | build/*
8 | dist/*
9 | .tox*
10 | .venv3*
11 | .coverage*
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: python
3 | python:
4 | - "2.7"
5 | - "3.5"
6 | install: pip install tox-travis
7 | script: tox
8 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Checksum Labs, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Ownercheck
2 |
3 | [](https://github.com/FallibleInc/ownercheck)
4 |
5 | `ownercheck` can be used to verify ownership of domains and mobile apps hosted on Android Play store or iOS app store. Domains can be verified by either adding a DNS record (CNAME or a TXT record) or by adding content to the existing website(a meta tag or uploading an empty file with the specified name). Mobile apps can be verified by checking the corresponding app page on app store for mention of user's verified domain in developers section of the page.
6 |
7 | The library uses SQLite to store and match generated verification codes, which can be easily swapped with any database backend you use (check db.py, need to make it configurable later).
8 |
9 | #### Installation
10 |
11 | ``` bash
12 | pip install ownercheck
13 | ````
14 |
15 | #### Running tests
16 |
17 | ````
18 | pip install tox
19 | tox
20 | ````
21 |
22 |
23 | #### How to use
24 |
25 | ##### Verify domain ownership
26 |
27 | ###### User has access to the domain DNS settings
28 |
29 | ``` python
30 | import ownercheck
31 |
32 | ownercheck.generate_code('example.com', 'CNAME')
33 | # Now proceed to add a DNS entry for CNAME
34 | ownercheck.verify_domain('example.com', 'CNAME')
35 | ```
36 |
37 | ###### User has access to the content hosted on the domain
38 |
39 | ``` python
40 | import ownercheck
41 |
42 | ownercheck.generate_code('example.com', 'METATAG')
43 | # Now proceed to add meta tag in your index.html as directed
44 | ownercheck.verify_domain('example.com', 'METATAG') # returns a bool
45 | ```
46 |
47 |
48 | ##### Fetch mobile app links from domain
49 |
50 | ``` python
51 | import ownercheck
52 |
53 | ownercheck.fetch_apps(domain)
54 | # Do not use this for mobile apps verification. A malacious user can link to apps they do not own.
55 | # Get all the link to apps on a page and then use ownercheck.verify_app instead.
56 | ```
57 |
58 |
59 | ##### Verify ownership of mobile apps on Play/App Store
60 |
61 | ``` python
62 | import ownercheck
63 |
64 | app_url = '' # Your Play store or App store published app URL
65 | domain = '' # Your domain related to the app
66 |
67 | ownercheck.verify_app(app_url, domain)
68 | ```
69 |
70 |
--------------------------------------------------------------------------------
/ownercheck/__init__.py:
--------------------------------------------------------------------------------
1 | from .domains import verify_domain
2 | from .apps import verify_app, fetch_apps
3 | from .db import generate_code
4 |
--------------------------------------------------------------------------------
/ownercheck/apps.py:
--------------------------------------------------------------------------------
1 | try:
2 | from HTMLParser import HTMLParser
3 | from urlparse import urlparse
4 | except ImportError:
5 | from html.parser import HTMLParser
6 | from urllib.parse import urlparse
7 |
8 |
9 | import requests
10 |
11 |
12 | class MobileAppLinksParser(HTMLParser):
13 |
14 | def __init__(self):
15 | HTMLParser.__init__(self)
16 | self.app_links = set([])
17 |
18 | def handle_starttag(self, tag, attrs):
19 | attrs = dict(attrs)
20 | if tag == 'a' and 'href' in attrs:
21 | parsed_url = urlparse(attrs['href'])
22 | if parsed_url.netloc == 'play.google.com':
23 | self.app_links.add((attrs['href'], 'Android'))
24 | elif parsed_url.netloc == 'itunes.apple.com':
25 | self.app_links.add((attrs['href'], 'iOS'))
26 |
27 |
28 | class AllLinksParser(HTMLParser):
29 |
30 | def __init__(self):
31 | HTMLParser.__init__(self)
32 | self.links = set([])
33 |
34 | def handle_starttag(self, tag, attrs):
35 | attrs = dict(attrs)
36 | if tag == 'a' and 'href' in attrs:
37 | self.links.add(attrs['href'])
38 |
39 |
40 | class InvalidAppStoreURL(Exception):
41 | pass
42 |
43 |
44 | def verify_app(appstore_url, domain):
45 | parsed_url = urlparse(appstore_url)
46 | app_store = None
47 | if parsed_url.netloc == 'play.google.com':
48 | app_store = 'Android'
49 | elif parsed_url.netloc == 'itunes.apple.com':
50 | app_store = 'iOS'
51 | else:
52 | raise InvalidAppStoreURL()
53 |
54 | parser = AllLinksParser()
55 | r = requests.get(appstore_url)
56 | parser.feed(r.text)
57 |
58 | for i in parser.links:
59 | if app_store == 'Android' and urlparse(i).netloc == 'www.google.com':
60 | try:
61 | # Google Play store has the developer website as a query param
62 | page_domain = urlparse(
63 | urlparse(i).query.split('&')[0].split('=')[1]).netloc
64 | if page_domain == domain:
65 | return True
66 | except:
67 | pass
68 | else:
69 | if urlparse(i).netloc == domain:
70 | return True
71 | return False
72 |
73 |
74 | def fetch_apps(url):
75 | r = requests.get(url)
76 | parser = MobileAppLinksParser()
77 | parser.feed(r.text)
78 | return list(parser.app_links)
79 |
--------------------------------------------------------------------------------
/ownercheck/conf.py:
--------------------------------------------------------------------------------
1 | # Types of verification checks possible
2 | CHECK_TYPES = ['CNAME', 'TXT', 'METATAG', 'FILE']
3 |
4 | # The CNAME value that the user has to set if using CNAME verification
5 | CNAME_VALUE = 'verify007dada.fallible.co'
6 |
7 | # The name of the meta tag the user has to create
8 | META_TAG_NAME = 'fallible'
9 |
10 | # Refer db.py
11 | SQLITE_TABLE = 'fa-ownercheck.db'
12 |
13 | # Some websites do not respond properly without appearing as browsers
14 | FAKE_USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0'
15 |
--------------------------------------------------------------------------------
/ownercheck/db.py:
--------------------------------------------------------------------------------
1 | """
2 | If you are using some other database, the functions can be safely
3 | replaced with your own but having the same functionality and interface.
4 | """
5 |
6 | import uuid
7 | import sqlite3
8 | from .conf import SQLITE_TABLE
9 |
10 |
11 | def setup_db():
12 | conn = sqlite3.connect(SQLITE_TABLE)
13 | cursor = conn.cursor()
14 | try:
15 | cursor.execute('''
16 | CREATE TABLE codes
17 | (domain text, checktype text, value text)''')
18 | conn.commit()
19 | except:
20 | pass
21 | finally:
22 | conn.close()
23 |
24 |
25 | def generate_code(domain, check_type):
26 | current_code = get_code(domain, check_type)
27 | if current_code is not None:
28 | return current_code
29 | uid = str(uuid.uuid4())
30 | conn = sqlite3.connect(SQLITE_TABLE)
31 | c = conn.cursor()
32 | c.execute('''
33 | INSERT INTO codes
34 | VALUES(?, ?, ?)''',
35 | (domain, check_type, uid))
36 | conn.commit()
37 | conn.close()
38 | return uid
39 |
40 |
41 | def get_code(domain, check_type):
42 | conn = sqlite3.connect(SQLITE_TABLE)
43 | cursor = conn.cursor()
44 | cursor.execute('''
45 | SELECT value FROM codes
46 | WHERE domain=? AND checktype=?''',
47 | (domain, check_type))
48 | row = cursor.fetchone()
49 | if row is not None:
50 | return row[0]
51 | else:
52 | return None
53 |
54 |
55 | def remove_code(domain, check_type):
56 | conn = sqlite3.connect(SQLITE_TABLE)
57 | cursor = conn.cursor()
58 | cursor.execute('''
59 | DELETE FROM codes WHERE domain=? AND checktype=?''',
60 | (domain, check_type))
61 | conn.commit()
62 | conn.close()
63 |
--------------------------------------------------------------------------------
/ownercheck/domains.py:
--------------------------------------------------------------------------------
1 | try:
2 | from HTMLParser import HTMLParser
3 | except ImportError:
4 | from html.parser import HTMLParser
5 | import requests
6 | import dns.resolver
7 | from .db import setup_db, get_code
8 | from .conf import CHECK_TYPES, CNAME_VALUE, META_TAG_NAME, FAKE_USER_AGENT
9 |
10 |
11 | # Create db tables if not already created
12 | setup_db()
13 |
14 |
15 | class InvalidVerificationType(Exception):
16 | pass
17 |
18 |
19 | class NoVerificationCodeExists(Exception):
20 | pass
21 |
22 |
23 | def _verify_cname(subdomain, expected_value=CNAME_VALUE):
24 | try:
25 | records = dns.resolver.query(subdomain, 'CNAME')
26 | record = records[0].to_text()
27 | if record.endswith('.'):
28 | record = record[:-1]
29 | if expected_value.endswith('.'):
30 | expected_value = expected_value[:-1]
31 | if record == expected_value:
32 | return True
33 | except dns.resolver.NoAnswer:
34 | return False
35 | except dns.resolver.Timeout:
36 | return False
37 | return False
38 |
39 |
40 | def _verify_txt_record(domain, expected_value):
41 | try:
42 | records = dns.resolver.query(domain, 'TXT')
43 | for r in records:
44 | record = r.to_text()
45 | if record.startswith('"') and record.endswith('"'):
46 | record = record[1:-1]
47 | if record == expected_value:
48 | return True
49 | except (dns.resolver.NoAnswer, dns.resolver.Timeout):
50 | return False
51 | return False
52 |
53 |
54 | class MetaTagParser(HTMLParser):
55 | """
56 | Given a meta tag name, saves it's content in value
57 | """
58 |
59 | def __init__(self, meta_tag_name):
60 | HTMLParser.__init__(self)
61 | self.value = None
62 | self.tag_name = meta_tag_name
63 |
64 | def handle_starttag(self, tag, attrs):
65 | attrs = dict(attrs)
66 | if tag.lower() == 'meta' and attrs.get('name') == self.tag_name:
67 | self.value = attrs['content']
68 |
69 |
70 | def _verify_meta_tag(domain, expected_value, tag=META_TAG_NAME):
71 | r = requests.get(
72 | 'http://' + domain,
73 | headers={
74 | 'User-Agent': FAKE_USER_AGENT})
75 | text = r.text
76 | parser = MetaTagParser(tag)
77 | parser.feed(text)
78 | return bool(parser.value == expected_value)
79 |
80 |
81 | def _verify_file_exists(domain, filename):
82 | r = requests.get('/'.join(['http:/', domain, filename]))
83 | return bool(200 <= r.status_code < 300)
84 |
85 |
86 | def verify_domain(domain, check_type):
87 | code = get_code(domain, check_type)
88 |
89 | if check_type not in CHECK_TYPES:
90 | raise InvalidVerificationType(
91 | '%s not in %s' % (check_type, str(CHECK_TYPES)))
92 |
93 | if code is None:
94 | raise NoVerificationCodeExists(
95 | 'No verification code found for %s'
96 | % str((domain, check_type)))
97 |
98 | response = False
99 |
100 | if check_type == 'CNAME':
101 | response = _verify_cname(code)
102 | elif check_type == 'TXT':
103 | response = _verify_txt_record(domain, code)
104 | elif check_type == 'METATAG':
105 | response = _verify_meta_tag(domain, code)
106 | elif check_type == 'FILE':
107 | response = _verify_file_exists(domain, code)
108 |
109 | return response
110 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | click==6.6
2 | cookies==2.2.1
3 | dnspython==1.14.0
4 | funcsigs==1.0.2
5 | mock==2.0.0
6 | pbr==1.10.0
7 | py==1.10.0
8 | pytest==3.0.1
9 | requests==2.20.0
10 | responses==0.5.1
11 | six==1.10.0
12 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name='ownercheck',
5 | version='0.2.1',
6 | description='Verify ownership of domain and mobile apps using DNS and other methods',
7 | author='Fallible',
8 | author_email='hello@fallible.co',
9 | url='https://github.com/FallibleInc/ownercheck',
10 | download_url='https://github.com/FallibleInc/ownercheck/tarball/0.2',
11 | packages=['ownercheck'],
12 | install_requires=[
13 | 'dnspython',
14 | 'requests',
15 | 'pytest',
16 | 'responses',
17 | ],
18 | )
19 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | from sys import path
2 | from os.path import dirname as dir
3 | path.append(dir(path[0]))
4 |
5 |
--------------------------------------------------------------------------------
/tests/conf.py:
--------------------------------------------------------------------------------
1 | TEST_DOMAIN = "google.com"
2 |
3 | IOS_URL = 'https://itunes.apple.com/in/app/twitter/id333903271?mt=8'
4 | ANDROID_URL = 'https://play.google.com/store/apps/details?id=com.twitter.android'
5 |
6 | WEB_URL = 'https://snapchat.com'
--------------------------------------------------------------------------------
/tests/data/google.web.html:
--------------------------------------------------------------------------------
1 |
Google
2 |
--------------------------------------------------------------------------------
/tests/data/index.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FallibleInc/ownercheck/f3d85f5e889b3815887da082911e6cabea542bc1/tests/data/index.html
--------------------------------------------------------------------------------
/tests/data/snapchat.web.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Snapchat
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | Want your own Snapchat Geofilter?
62 |
63 |
64 |
74 |
75 |
76 |
164 |
165 |
166 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/tests/data/twitter.ios.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Twitter on the App Store
12 |
13 |
14 |
15 |
16 |
17 |
18 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
107 |
108 |
109 | Apple
110 |
111 |
114 |
117 |
120 |
123 |
126 |
129 |
132 |
135 |
138 |
139 |
140 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
Opening the iTunes Store. If iTunes doesn’t open, click the iTunes icon in your Dock or on your Windows desktop. Progress Indicator
164 |
Opening the iBooks Store. If iBooks doesn't open, click the iBooks app in your Dock. Progress Indicator
165 |
166 |
167 |
168 |
169 |
170 |
iTunes
171 |
172 |
iTunes is the world's easiest way to organize and add to your digital media collection.
173 |
174 |
175 | We are unable to find iTunes on your computer. To download the free app Twitter by Twitter, Inc., get iTunes now.
176 |
177 |
178 |
179 |
Do you already have iTunes? Click I Have iTunes to open it now.
180 |
181 |
182 |
183 |
184 |
185 |
186 |
iTunes for Mac + PC
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
Twitter
209 |
By Twitter, Inc.
210 |
Essentials
211 |
212 |
217 |
Open iTunes to buy and download apps.
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 | Description
227 |
228 |
229 |
230 |
231 |
232 |
See what’s happening in the world right now. From breaking news and entertainment, sports and politics, to big events and everyday interests. If it’s happening anywhere, it’s happening on Twitter. Get the full story as it unfolds, with all the live commentary. Be part of what everyone is talking about and get videos, live footage and Moments, direct from the source. Join in on all the action by sharing what’s happening in your world. On Twitter you can post photos with stickers, GIFs, videos, and even stream live video with the Periscope button. There is no better way to have your voice heard.
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 | What's New in Version 6.60
247 |
248 |
249 |
250 |
251 |
252 |
See your Tweets when the lights go out. Night Mode delivers a dark color palette that's made for reading in the dark. Plus, we've released new settings that give you more control over what you see on Twitter.
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
Screenshots
269 |
270 |
271 |
272 |
273 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
Apple Watch
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
Customer Reviews
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 | Good!! Need Improvements
330 |
331 |
332 |
333 |
334 |
by
335 |
336 | Aneezx
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 | The app is good after the latest night mode. There is one thing that the night mode s not effective in the tweet activity screen. Would be good if it reflects there too.
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 | List Tab not visible
363 |
364 |
365 |
366 |
367 |
by
368 |
369 | Latesh Narula
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 | The List tab available on twitter while we access it with web browsers , is absent on the Twitter app. This is a must needed thing . I have to login to twitter via web brower due to the same not availabe on app.
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 | Twitter is paying my bills.
396 |
397 |
398 |
399 |
400 |
by
401 |
402 | Ohmygloss
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 | Love it. If you don't understand it, don't delete it.. Hang on, you'll get a hang of it. It's really nice after that.
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
View in iTunes
431 |
This app is designed for both iPhone and iPad
Offers Apple Watch App for iPhone Free
Category: News Updated: 19 August 2016 Version: 6.60 Size: 139 MBApple Watch: YesLanguages: English, Arabic, Catalan, Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Malay, Norwegian Bokmål, Polish, Portuguese, Romanian, Russian, Simplified Chinese, Slovak, Spanish, Swedish, Thai, Traditional Chinese, Turkish, Ukrainian, VietnameseDeveloper: Twitter, Inc. © Twitter, Inc. Compatibility: Requires iOS 8.1 or later. Compatible with iPhone, iPad, and iPod touch.
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
665 |
761 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 |
--------------------------------------------------------------------------------
/tests/test_apps.py:
--------------------------------------------------------------------------------
1 | import io
2 | import re
3 | import responses
4 | from ownercheck.apps import verify_app, fetch_apps
5 | from .conf import IOS_URL, ANDROID_URL, WEB_URL
6 |
7 |
8 | @responses.activate
9 | def test_fetch_apps():
10 |
11 | responses.add(responses.GET,
12 | WEB_URL,
13 | body=io.open('tests/data/snapchat.web.html', encoding='utf-8').read(),
14 | status=200,
15 | content_type='text/html')
16 | assert len(fetch_apps(WEB_URL)) == 2
17 | app_types = [i[1] for i in fetch_apps(WEB_URL)]
18 | app_types.sort()
19 | assert app_types == ['Android', 'iOS']
20 |
21 |
22 | @responses.activate
23 | def test_verify_app():
24 | responses.add(responses.GET,
25 | ANDROID_URL,
26 | body=io.open('tests/data/twitter.android.html', encoding='utf-8').read(),
27 | status=200,
28 | content_type='text/html',
29 | match_querystring=True)
30 | responses.add(responses.GET,
31 | IOS_URL,
32 | body=io.open('tests/data/twitter.ios.html', encoding='utf-8').read(),
33 | status=200,
34 | content_type='text/html',
35 | match_querystring=True)
36 | assert verify_app(ANDROID_URL, 'twitter.com') == True
37 | assert verify_app(IOS_URL, 'twitter.com') == True
38 |
39 |
--------------------------------------------------------------------------------
/tests/test_db.py:
--------------------------------------------------------------------------------
1 | from ownercheck.db import setup_db, generate_code, get_code, remove_code
2 | from .conf import TEST_DOMAIN
3 |
4 |
5 | def test_generate_code():
6 | setup_db()
7 | assert generate_code(TEST_DOMAIN, 'TXT') != None
8 | assert generate_code(TEST_DOMAIN, 'TXT') != generate_code(
9 | TEST_DOMAIN, 'CNAME')
10 | assert generate_code(TEST_DOMAIN, 'TXT') == generate_code(
11 | TEST_DOMAIN, 'TXT')
12 |
13 |
14 | def test_get_code():
15 | setup_db()
16 | code = generate_code(TEST_DOMAIN, 'FILE')
17 | assert get_code(TEST_DOMAIN, 'FILE') == code
18 | assert get_code(TEST_DOMAIN, 'METATAG') == None
19 |
20 |
21 | def test_remove_code():
22 | setup_db()
23 | file_code = generate_code(TEST_DOMAIN, 'FILE')
24 | cname_code = generate_code(TEST_DOMAIN, 'CNAME')
25 | remove_code(TEST_DOMAIN, 'FILE')
26 | new_file_code = generate_code(TEST_DOMAIN, 'FILE')
27 | new_cname_code = generate_code(TEST_DOMAIN, 'CNAME')
28 | assert file_code != new_file_code
29 | assert cname_code == new_cname_code
30 |
31 |
--------------------------------------------------------------------------------
/tests/test_domains.py:
--------------------------------------------------------------------------------
1 | import io
2 | import uuid
3 | import subprocess
4 | import responses
5 | from ownercheck.domains import (_verify_cname,
6 | _verify_txt_record,
7 | _verify_meta_tag,
8 | _verify_file_exists,
9 | verify_domain)
10 | from ownercheck.db import generate_code
11 | from .conf import TEST_DOMAIN
12 |
13 |
14 | def test_verify_cname():
15 | expected = 'www3.l.google.com.'
16 | if expected.endswith("'"):
17 | expected = expected.rstrip("'")
18 | if expected.endswith("\\n"):
19 | expected = expected[:-2]
20 | print(expected)
21 | assert _verify_cname('developers.google.com', expected) == True
22 | assert _verify_cname('developers.google.com', str(uuid.uuid4())) == False
23 |
24 |
25 | def test_verify_txt_record():
26 | expected = "v=spf1 include:_spf.google.com ~all"
27 | assert _verify_txt_record("google.com", expected) == True
28 | assert _verify_txt_record("google.com", str(uuid.uuid4())) == False
29 |
30 |
31 | @responses.activate
32 | def test_verify_meta_tag():
33 | responses.add(responses.GET,
34 | 'http://' + TEST_DOMAIN,
35 | body=io.open('tests/data/google.web.html', encoding='utf-8').read(),
36 | status=200,
37 | content_type='text/html')
38 | assert _verify_meta_tag(TEST_DOMAIN, "origin", "referrer") == True
39 | assert _verify_meta_tag(TEST_DOMAIN, str(uuid.uuid4()), "fallible") == False
40 |
41 |
42 | @responses.activate
43 | def test_verify_file_exists():
44 | responses.add(responses.GET,
45 | 'http://' + TEST_DOMAIN + '/robots.txt',
46 | body='',
47 | status=200,
48 | content_type='text/html')
49 | random_str = str(uuid.uuid4())
50 | responses.add(responses.GET,
51 | 'http://' + TEST_DOMAIN + '/' + random_str,
52 | body='',
53 | status=404,
54 | content_type='text/html')
55 | assert _verify_file_exists(TEST_DOMAIN, "robots.txt") == True
56 | assert _verify_file_exists(TEST_DOMAIN, random_str) == False
57 |
58 |
59 | def test_verify_domain():
60 | pass
61 |
62 |
63 |
64 | # def expected_cname():
65 | # p = subprocess.Popen(['host', '-t', 'CNAME', 'developers.google.com'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
66 | # out, err = p.communicate()
67 | # expected = str(out).split(' ')[-1].strip()
68 | # return expected
69 |
70 | # def expected_txt():
71 | # p = subprocess.Popen(['host', '-t', 'TXT', 'google.com'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
72 | # out, err = p.communicate()
73 | # expected = None
74 | # try:
75 | # expected = str(out).split('"')[-2].strip()
76 | # except:
77 | # pass
78 | # return expected
79 |
80 |
81 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | #skipsdist = true
3 | envlist = py27,py35
4 |
5 | [testenv]
6 | deps = -rrequirements.txt
7 | commands = py.test
8 |
9 |
10 |
--------------------------------------------------------------------------------