This repository has been archived by the owner on Mar 15, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 235
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3537 from diox/remove-iarc-waffle
Remove old IARC implementation
- Loading branch information
Showing
48 changed files
with
935 additions
and
2,803 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1 @@ | ||
import client # noqa | ||
import utils # noqa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,162 +1,212 @@ | ||
# -*- coding: utf8 -*- | ||
import base64 | ||
import functools | ||
import os | ||
import datetime | ||
import requests | ||
from urlparse import urljoin | ||
from uuid import UUID | ||
|
||
from django.conf import settings | ||
from django.db import transaction | ||
|
||
import commonware.log | ||
from django_statsd.clients import statsd | ||
from suds import client as sudsclient | ||
from post_request_task.task import task | ||
|
||
from lib.iarc.serializers import IARCRatingListSerializer | ||
from mkt.site.helpers import absolutify | ||
from mkt.translations.utils import no_translation | ||
from mkt.users.models import UserProfile | ||
|
||
log = commonware.log.getLogger('z.iarc') | ||
|
||
|
||
root = os.path.join(settings.ROOT, 'lib', 'iarc', 'wsdl', settings.IARC_ENV) | ||
wsdl = { | ||
'services': 'file://' + os.path.join(root, 'iarc_services.wsdl'), | ||
} | ||
|
||
# Add a list of supported methods here. | ||
services = ['Get_App_Info', 'Set_Storefront_Data', 'Get_Rating_Changes'] | ||
|
||
|
||
class Client(object): | ||
""" | ||
IARC SOAP client. | ||
A wrapper around suds to make calls to IARC, leaving room for future WSDL | ||
expansion. Example usage:: | ||
client = Client('services') | ||
response = client.Get_App_Info(XMLString=xml) | ||
print response # response is already base64 decoded. | ||
class IARCException(Exception): | ||
pass | ||
|
||
|
||
def _iarc_headers(): | ||
"""HTTP headers to include in each request to IARC.""" | ||
return { | ||
'StoreID': settings.IARC_V2_STORE_ID, | ||
'StorePassword': settings.IARC_V2_STORE_PASSWORD, | ||
} | ||
|
||
|
||
def _iarc_request(endpoint_name, data): | ||
"""Wrapper around requests.post which handles url generation from the | ||
endpoint_name and auth headers. Returns data as a dict.""" | ||
url = urljoin(settings.IARC_V2_SERVICE_ENDPOINT, endpoint_name) | ||
headers = _iarc_headers() | ||
response = requests.post(url, headers=headers, json=data) | ||
try: | ||
value = response.json() | ||
except ValueError: | ||
value = {} | ||
return value | ||
|
||
|
||
def _iarc_app_data(app): | ||
"""App data that IARC needs in PushCert response / AttachToCert request.""" | ||
from mkt.webapps.models import Webapp | ||
|
||
author = app.listed_authors[0] if app.listed_authors else UserProfile() | ||
with no_translation(app.default_locale): | ||
app_name = unicode(Webapp.with_deleted.get(pk=app.pk).name) | ||
data = { | ||
'Publish': app.is_public(), | ||
'ProductName': app_name, | ||
'StoreProductID': app.guid, | ||
'StoreProductURL': absolutify(app.get_url_path()), | ||
# We want an identifier that does not change when users attached to | ||
# an app are shuffled around, so just use the app PK as developer id. | ||
'StoreDeveloperID': app.pk, | ||
# PushCert and AttachToCert docs use a different property for the | ||
# developer email address, use both just in case. | ||
'DeveloperEmail': author.email, | ||
'EmailAddress': author.email, | ||
'CompanyName': app.developer_name, | ||
} | ||
return data | ||
|
||
|
||
def get_rating_changes(date=None): | ||
""" | ||
Call GetRatingChange to get all changes from IARC within 24 hours of the | ||
specified date (using today by default), and apply them to the | ||
corresponding Webapps. | ||
def __init__(self, wsdl_name): | ||
self.wsdl_name = wsdl_name | ||
self.client = None | ||
|
||
def __getattr__(self, attr): | ||
for name, methods in [('services', services)]: | ||
if attr in methods: | ||
return functools.partial(self.call, attr, wsdl=name) | ||
raise AttributeError('Unknown request: %s' % attr) | ||
|
||
def call(self, name, **data): | ||
log.info('IARC client call: {0} from wsdl: {1}'.format(name, wsdl)) | ||
|
||
if self.client is None: | ||
self.client = sudsclient.Client(wsdl[self.wsdl_name], cache=None) | ||
|
||
# IARC requires messages be base64 encoded and base64 requires | ||
# byte-strings. | ||
for k, v in data.items(): | ||
if isinstance(v, unicode): | ||
# Encode it as a byte-string. | ||
v = v.encode('utf-8') | ||
data[k] = base64.b64encode(v) | ||
|
||
with statsd.timer('mkt.iarc.request.%s' % name.lower()): | ||
response = getattr(self.client.service, name)(**data) | ||
|
||
return base64.b64decode(response) | ||
|
||
|
||
class MockClient(Client): | ||
FIXME: Could add support for pagination, but very low priority since we | ||
will never ever get anywhere close to 500 rating changes in a single day. | ||
""" | ||
Mocked IARC SOAP client. | ||
""" | ||
|
||
def call(self, name, **data): | ||
responses = { | ||
'Get_App_Info': MOCK_GET_APP_INFO, | ||
'Set_Storefront_Data': MOCK_SET_STOREFRONT_DATA, | ||
'Get_Rating_Changes': MOCK_GET_RATING_CHANGES, | ||
} | ||
|
||
return responses.get(name, '') | ||
|
||
|
||
def get_iarc_client(wsdl): | ||
from mkt.webapps.models import IARCCert | ||
|
||
# EndDate should be the most recent one. | ||
end_date = date or datetime.datetime.utcnow() | ||
start_date = end_date - datetime.timedelta(days=1) | ||
log.info('Calling IARC GetRatingChanges from %s to %s', | ||
start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d')) | ||
data = _iarc_request('GetRatingChanges', { | ||
'StartDate': start_date.strftime('%Y-%m-%d'), | ||
'EndDate': end_date.strftime('%Y-%m-%d'), | ||
'MaxRows': 500, # Limit. | ||
'StartRowIndex': 0 # Offset. | ||
}) | ||
for row in data.get('CertList', []): | ||
# Find app through Cert ID, ignoring unknown certs. | ||
try: | ||
cert = IARCCert.objects.get(cert_id=UUID(row['CertID']).get_hex()) | ||
except IARCCert.DoesNotExist: | ||
continue | ||
serializer = IARCRatingListSerializer(instance=cert.app, data=row) | ||
if serializer.is_valid(): | ||
rating_bodies = [body.id for body | ||
in serializer.validated_data['ratings'].keys()] | ||
serializer.save(rating_bodies=rating_bodies) | ||
return data | ||
|
||
|
||
@transaction.atomic | ||
def search_and_attach_cert(app, cert_id): | ||
"""Call SearchCerts to get all info about an existing cert from IARC and | ||
apply that info to the Webapp instance passed. Then, call AttachToCert | ||
to notify IARC that we're attaching the cert to that Webapp.""" | ||
serializer = search_cert(app, cert_id) | ||
if serializer.is_valid(): | ||
serializer.save() | ||
else: | ||
raise IARCException('SearchCerts failed, invalid data!') | ||
data = _attach_to_cert(app, cert_id) | ||
if data.get('ResultCode') != 'Success': | ||
# If AttachToCert failed, we need to rollback the save we did earlier, | ||
# we raise an exception to do that since we are in an @atomic block. | ||
raise IARCException(data.get('ErrorMessage', 'AttachToCert failed!')) | ||
return data | ||
|
||
|
||
def search_cert(app, cert_id): | ||
"""Ask IARC for information about a cert.""" | ||
log.info('Calling IARC SearchCert for app %s, cert %s', | ||
app.pk, unicode(UUID(cert_id))) | ||
data = _iarc_request('SearchCerts', {'CertID': unicode(UUID(cert_id))}) | ||
# We don't care about MatchFound, serializer won't find the right fields | ||
# if no match is found. | ||
serializer = IARCRatingListSerializer(instance=app, data=data) | ||
return serializer | ||
|
||
|
||
def _attach_to_cert(app, cert_id): | ||
"""Tell IARC to attach a cert to an app.""" | ||
data = _iarc_app_data(app) | ||
data['CertID'] = unicode(UUID(cert_id)) | ||
log.info('Calling IARC AttachToCert for app %s, cert %s', | ||
app.pk, data['CertID']) | ||
return _iarc_request('AttachToCert', data) | ||
|
||
|
||
@task | ||
def publish(app_id): | ||
"""Delayed task to tell IARC we published an app.""" | ||
from mkt.webapps.models import IARCCert, Webapp | ||
|
||
try: | ||
app = Webapp.with_deleted.get(pk=app_id) | ||
except Webapp.DoesNotExist: | ||
return | ||
try: | ||
cert_id = app.iarc_cert.cert_id | ||
data = _update_certs(app_id, cert_id, 'Publish') | ||
except IARCCert.DoesNotExist: | ||
data = None | ||
return data | ||
|
||
|
||
@task | ||
def unpublish(app_id): | ||
"""Delayed task to tell IARC we unpublished an app.""" | ||
from mkt.webapps.models import IARCCert, Webapp | ||
|
||
try: | ||
app = Webapp.with_deleted.get(pk=app_id) | ||
except Webapp.DoesNotExist: | ||
return | ||
try: | ||
cert_id = app.iarc_cert.cert_id | ||
data = _update_certs(app_id, cert_id, 'Unpublish') | ||
except IARCCert.DoesNotExist: | ||
data = None | ||
return data | ||
|
||
|
||
def refresh(app): | ||
"""Refresh an app IARC information by asking IARC about its certificate.""" | ||
serializer = search_cert(app, app.iarc_cert.cert_id) | ||
serializer.save() | ||
|
||
|
||
# FIXME: implement UpdateStoreAttributes for when the app developer email | ||
# changes. | ||
|
||
|
||
def _update_certs(app_id, cert_id, action): | ||
""" | ||
Use this to get the right client and communicate with IARC. | ||
UpdateCerts to tell IARC when we publish or unpublish a product. | ||
Endpoint can handle batch updates, but we only need one at a time. | ||
Arguments: | ||
cert_id -- Globally unique ID for certificate. | ||
action -- One of [InvalidateCert, Unpublish, UpdateStoreAttributes, | ||
Publish]. | ||
Return: | ||
Update object. | ||
ResultCode (string) -- Success or Failure | ||
ErrorId (string) -- Can pass on to IARC for debugging. | ||
ErrorMessage (string) -- Human-readable error message | ||
""" | ||
if settings.IARC_MOCK: | ||
return MockClient(wsdl) | ||
return Client(wsdl) | ||
|
||
|
||
MOCK_GET_APP_INFO = '''<?xml version="1.0" encoding="utf-16"?> | ||
<WEBSERVICE xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SERVICE_NAME="GET_APP_INFO" TYPE="RESPONSE"> | ||
<ROW> | ||
<FIELD NAME="rowId" TYPE="int" VALUE="1" /> | ||
<FIELD NAME="submission_id" TYPE="string" VALUE="52" /> | ||
<FIELD NAME="title" TYPE="string" VALUE="Twitter" /> | ||
<FIELD NAME="company" TYPE="string" VALUE="Mozilla" /> | ||
<FIELD NAME="platform" TYPE="string" VALUE="Firefox" /> | ||
<FIELD NAME="rating_PEGI" TYPE="string" VALUE="16+" /> | ||
<FIELD NAME="descriptors_PEGI" TYPE="string" VALUE="Language, Online" /> | ||
<FIELD NAME="rating_USK" TYPE="string" VALUE="Rating Refused" /> | ||
<FIELD NAME="descriptors_USK" TYPE="string" VALUE="Explizite Sprache" /> | ||
<FIELD NAME="rating_ESRB" TYPE="string" VALUE="Mature 17+" /> | ||
<FIELD NAME="descriptors_ESRB" TYPE="string" VALUE="Strong Language" /> | ||
<FIELD NAME="rating_CLASSIND" TYPE="string" VALUE="14+" /> | ||
<FIELD NAME="descriptors_CLASSIND" TYPE="string" VALUE="Linguagem Impr\xc3\xb3pria" /> | ||
<FIELD NAME="rating_Generic" TYPE="string" VALUE="16+" /> | ||
<FIELD NAME="descriptors_Generic" TYPE="string" VALUE="" /> | ||
<FIELD NAME="storefront" TYPE="string" VALUE="Mozilla" /> | ||
<FIELD NAME="interactive_elements" TYPE="string" VALUE="Shares Info, Shares Location, Digital Purchases, Users Interact, " /> | ||
</ROW> | ||
</WEBSERVICE> | ||
''' # noqa | ||
|
||
|
||
MOCK_SET_STOREFRONT_DATA = '''<?xml version="1.0" encoding="utf-16"?> | ||
<WEBSERVICE xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SERVICE_NAME="SET_STOREFRONT_DATA" TYPE="RESPONSE"> | ||
<ROW> | ||
<FIELD NAME="action_status" TYPE="string" VALUE="OK - Storefront data Updated" /> | ||
<FIELD NAME="submission_id" TYPE="string" VALUE="52" /> | ||
<FIELD NAME="security_code" TYPE="string" VALUE="FZ32CU8" /> | ||
<FIELD NAME="title" TYPE="string" VALUE="Twitter" /> | ||
<FIELD NAME="company" TYPE="string" VALUE="Mozilla" /> | ||
<FIELD NAME="rating" TYPE="string" VALUE="16+" /> | ||
<FIELD NAME="descriptors" TYPE="string" VALUE="Language, Online" /> | ||
<FIELD NAME="interactive_elements" TYPE="string" VALUE="Shares Info, Shares Location, Digital Purchases, Users Interact, " /> | ||
</ROW> | ||
</WEBSERVICE> | ||
''' # noqa | ||
|
||
|
||
MOCK_GET_RATING_CHANGES = '''<?xml version="1.0" encoding="utf-16"?> | ||
<WEBSERVICE xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SERVICE_NAME="GET_RATING_CHANGES" TYPE="RESPONSE"> | ||
<ROW> | ||
<FIELD NAME="rowId" TYPE="int" VALUE="1" /> | ||
<FIELD NAME="change_date" TYPE="string" VALUE="11/12/2013" /> | ||
<FIELD NAME="submission_id" TYPE="string" VALUE="52" /> | ||
<FIELD NAME="security_code" TYPE="string" VALUE="FZ32CU8" /> | ||
<FIELD NAME="title" TYPE="string" VALUE="Twitter" /> | ||
<FIELD NAME="company" TYPE="string" VALUE="Mozilla" /> | ||
<FIELD NAME="email" TYPE="string" VALUE="nobody@mozilla.com" /> | ||
<FIELD NAME="new_rating" TYPE="string" VALUE="14+" /> | ||
<FIELD NAME="new_descriptors" TYPE="string" VALUE="Linguagem Impr\xc3\xb3pria" /> | ||
<FIELD NAME="rating_system" TYPE="string" VALUE="CLASSIND" /> | ||
<FIELD NAME="change_reason" TYPE="string" VALUE="Significant issues found in special mission cut scenes." /> | ||
</ROW> | ||
<ROW> | ||
<FIELD NAME="rowId" TYPE="int" VALUE="2" /> | ||
<FIELD NAME="change_date" TYPE="string" VALUE="11/12/2013" /> | ||
<FIELD NAME="submission_id" TYPE="string" VALUE="68" /> | ||
<FIELD NAME="security_code" TYPE="string" VALUE="GZ32CU8" /> | ||
<FIELD NAME="title" TYPE="string" VALUE="Other App" /> | ||
<FIELD NAME="company" TYPE="string" VALUE="Mozilla" /> | ||
<FIELD NAME="email" TYPE="string" VALUE="nobody@mozilla.com" /> | ||
<FIELD NAME="new_rating" TYPE="string" VALUE="Rating Refused" /> | ||
<FIELD NAME="new_descriptors" TYPE="string" VALUE="Explizite Sprache" /> | ||
<FIELD NAME="rating_system" TYPE="string" VALUE="USK" /> | ||
<FIELD NAME="change_reason" TYPE="string" VALUE="Discrimination found to be within German law." /> | ||
</ROW> | ||
</WEBSERVICE> | ||
''' # noqa | ||
data = { | ||
'UpdateList': [{ | ||
'CertID': unicode(UUID(cert_id)), | ||
'Action': action, | ||
}] | ||
} | ||
log.info('Calling IARC UpdateCert %s for app %s, cert %s', | ||
action, app_id, data['UpdateList'][0]['CertID']) | ||
return _iarc_request('UpdateCerts', data) |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.