/**
  * Get the expected name for the manifest cookie.
  *
  * @return the name (null if none)
  */
 private static String getManifestCookieName() {
   AuraContext context = Aura.getContextService().getCurrentContext();
   if (context.getApplicationDescriptor() != null) {
     StringBuilder sb = new StringBuilder();
     if (context.getMode() != Mode.PROD) {
       sb.append(context.getMode());
       sb.append("_");
     }
     sb.append(context.getApplicationDescriptor().getNamespace());
     sb.append("_");
     sb.append(context.getApplicationDescriptor().getName());
     sb.append(MANIFEST_COOKIE_TAIL);
     return sb.toString();
   }
   return null;
 }
  /**
   * check the top level component/app and get dependencies.
   *
   * <p>This routine checks to see that we have a valid top level component. If our top level
   * component is out of sync, we have to ignore it here, but we _must_ force the client to not
   * cache the response.
   *
   * <p>If there is a QFE, we substitute the QFE descriptor for the one given us, and continue.
   * Again, we cannot allow caching.
   *
   * <p>Finally, if there is no descriptor given, we simply ignore the request and give them an
   * empty response. Which is done here by returning null.
   *
   * <p>Also note that this handles the 'if-modified-since' header, as we want to tell the browser
   * that nothing changed in that case.
   *
   * @param request the request (for exception handling)
   * @param response the response (for exception handling)
   * @param context the context to get the definition.
   * @return the set of descriptors we are sending back, or null in the case that we handled the
   *     response.
   * @throws IOException if there was an IO exception handling a client out of sync exception
   * @throws ServletException if there was a problem handling the out of sync
   */
  @Override
  public Set<DefDescriptor<?>> verifyTopLevel(
      HttpServletRequest request, HttpServletResponse response, AuraContext context)
      throws IOException {
    DefDescriptor<? extends BaseComponentDef> appDesc = context.getApplicationDescriptor();
    MasterDefRegistry mdr = context.getDefRegistry();

    context.setPreloading(true);
    if (appDesc == null) {
      //
      // This means we have nothing to say to the client, so the response is
      // left completely empty.
      //
      return null;
    }
    long ifModifiedSince = request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
    String uid = context.getUid(appDesc);
    try {
      try {
        definitionService.updateLoaded(appDesc);
        if (uid != null && ifModifiedSince != -1) {
          //
          // In this case, we have an unmodified descriptor, so just tell
          // the client that.
          //
          response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
          return null;
        }
      } catch (ClientOutOfSyncException coose) {
        //
        // We can't actually handle an out of sync here, since we are doing a
        // resource load. We have to ignore it, and continue as if nothing happened.
        // But in the process, we make sure to set 'no-cache' so that the result
        // is thrown away. This may actually not give the right result in bizarre
        // corner cases... beware cache inconsistencies on revert after a QFE.
        //
        // We actually probably should do something different, like send a minimalist
        // set of stuff to make the client re-try.
        //
        this.setNoCache(response);
        String oosUid = mdr.getUid(null, appDesc);
        return mdr.getDependencies(oosUid);
      }
    } catch (QuickFixException qfe) {
      //
      // A quickfix exception means that we couldn't compile something.
      // In this case, we still want to preload things, but we want to preload
      // quick fix values, note that we force NoCache here.
      //
      this.setNoCache(response);
      this.handleServletException(qfe, true, context, request, response, true);
      return null;
    }
    this.setLongCache(response);
    if (uid == null) {
      uid = context.getUid(appDesc);
    }
    return mdr.getDependencies(uid);
  }
Exemple #3
0
  /**
   * Gets the UID for the application descriptor of the current context, or {@code null} if there is
   * no application (probably because of a compile error).
   */
  public String getContextAppUid(AuraContext context) {
    DefDescriptor<? extends BaseComponentDef> app = context.getApplicationDescriptor();

    if (app != null) {
      try {
        return definitionService.getDefRegistry().getUid(null, app);
      } catch (QuickFixException e) {
        // This is perfectly possible, but the error is handled in more
        // contextually-sensible places. For here, we know there's no
        // meaningful uid, so we fall through and return null.
      }
    }
    return null;
  }
  /** Is AppCache allowed by the current configuration? */
  public static boolean isManifestEnabled() {
    if (!Aura.getConfigAdapter().isClientAppcacheEnabled()) {
      return false;
    }

    AuraContext context = Aura.getContextService().getCurrentContext();
    DefDescriptor<? extends BaseComponentDef> appDefDesc = context.getApplicationDescriptor();

    if (appDefDesc != null && appDefDesc.getDefType().equals(DefType.APPLICATION)) {
      try {
        Boolean useAppcache = ((ApplicationDef) appDefDesc.getDef()).isAppcacheEnabled();
        if (useAppcache != null) {
          return useAppcache.booleanValue();
        }
        return false;
      } catch (QuickFixException e) {
        return false;
      }
    }
    return false;
  }
  @Override
  public void assertAccess(DefDescriptor<?> desc) throws QuickFixException {

    if (!accessCache.contains(desc)) {
      Aura.getLoggingService().incrementNum("SecurityProviderCheck");
      DefType defType = desc.getDefType();
      String ns = desc.getNamespace();
      AuraContext context = Aura.getContextService().getCurrentContext();
      Mode mode = context.getMode();
      String prefix = desc.getPrefix();
      //
      // This breaks encapsulation! -gordon
      //
      boolean isTopLevel = desc.equals(context.getApplicationDescriptor());

      if (isTopLevel) {
        //
        // If we are trying to access the top level component, we need to ensure
        // that it is _not_ abstract.
        //
        BaseComponentDef def = getDef(context.getApplicationDescriptor());
        if (def != null && def.isAbstract() && def.getProviderDescriptor() == null) {
          throw new NoAccessException(
              String.format("Access to %s disallowed. Abstract definition.", desc));
        }
      }
      //
      // If this is _not_ the top level, we allow circumventing the security provider.
      // This means that certain things will short-circuit, hopefully making checks faster...
      // Not sure if this is premature optimization or not.
      //
      if (!isTopLevel || desc.getDefType().equals(DefType.COMPONENT)) {
        if (!securedDefTypes.contains(defType)
            || unsecuredPrefixes.contains(prefix)
            || unsecuredNamespaces.contains(ns)
            || (mode != Mode.PROD
                && (!Aura.getConfigAdapter().isProduction())
                && unsecuredNonProductionNamespaces.contains(ns))) {
          accessCache.add(desc);
          return;
        }

        if (ns != null && DefDescriptor.JAVA_PREFIX.equals(prefix)) {
          // handle java packages that have namespaces like aura.impl.blah
          for (String okNs : unsecuredNamespaces) {
            if (ns.startsWith(okNs)) {
              accessCache.add(desc);
              return;
            }
          }
        }
      }

      SecurityProviderDef securityProviderDef = getSecurityProvider();
      if (securityProviderDef == null) {
        if (mode != Mode.PROD && !Aura.getConfigAdapter().isProduction()) {
          accessCache.add(desc);
          return;
        } else {
          throw new NoAccessException(
              String.format("Access to %s disallowed.  No Security Provider found.", desc));
        }
      } else {
        if (!securityProviderDef.isAllowed(desc)) {
          throw new NoAccessException(
              String.format(
                  "Access to %s disallowed by %s",
                  desc, securityProviderDef.getDescriptor().getName()));
        }
      }
      accessCache.add(desc);
    }
  }
  /**
   * Handle an exception in the servlet.
   *
   * <p>This routine should be called whenever an exception has surfaced to the top level of the
   * servlet. It should not be overridden unless Aura is entirely subsumed. Most special cases can
   * be handled by the Aura user by implementing {@link ExceptionAdapter ExceptionAdapter}.
   *
   * @param t the throwable to write out.
   * @param quickfix is this exception a valid quick-fix
   * @param context the aura context.
   * @param request the request.
   * @param response the response.
   * @param written true if we have started writing to the output stream.
   * @throws IOException if the output stream does.
   * @throws ServletException if send404 does (should not generally happen).
   */
  @Override
  public void handleServletException(
      Throwable t,
      boolean quickfix,
      AuraContext context,
      HttpServletRequest request,
      HttpServletResponse response,
      boolean written)
      throws IOException {
    try {
      Throwable mappedEx = t;
      boolean map = !quickfix;
      Format format = context.getFormat();

      //
      // This seems to fail, though the documentation implies that you can do
      // it.
      //
      // if (written && !response.isCommitted()) {
      // response.resetBuffer();
      // written = false;
      // }
      if (!written) {
        // Should we only delete for JSON?
        setNoCache(response);
      }
      if (mappedEx instanceof IOException) {
        //
        // Just re-throw IOExceptions.
        //
        throw (IOException) mappedEx;
      } else if (mappedEx instanceof NoAccessException) {
        Throwable cause = mappedEx.getCause();
        String denyMessage = mappedEx.getMessage();

        map = false;
        if (cause != null) {
          //
          // Note that the exception handler can remap the cause here.
          //
          cause = exceptionAdapter.handleException(cause);
          denyMessage += ": cause = " + cause.getMessage();
        }
        //
        // Is this correct?!?!?!
        //
        if (format != Format.JSON) {
          this.send404(request.getServletContext(), request, response);
          if (!isProductionMode(context.getMode())) {
            // Preserve new lines and tabs in the stacktrace since this is directly being written on
            // to the
            // page
            denyMessage = "<pre>" + AuraTextUtil.escapeForHTML(denyMessage) + "</pre>";
            response.getWriter().println(denyMessage);
          }
          return;
        }
      } else if (mappedEx instanceof QuickFixException) {
        if (isProductionMode(context.getMode())) {
          //
          // In production environments, we want wrap the quick-fix. But be a little careful here.
          // We should never mark the top level as a quick-fix, because that means that we gack
          // on every mis-spelled app. In this case we simply send a 404 and bolt.
          //
          if (mappedEx instanceof DefinitionNotFoundException) {
            DefinitionNotFoundException dnfe = (DefinitionNotFoundException) mappedEx;

            if (dnfe.getDescriptor() != null
                && dnfe.getDescriptor().equals(context.getApplicationDescriptor())) {
              // We're in production and tried to hit an aura app that doesn't exist.
              // just show the standard 404 page.
              this.send404(request.getServletContext(), request, response);
              return;
            }
          }
          map = true;
          mappedEx = new AuraUnhandledException("404 Not Found (Application Error)", mappedEx);
        }
      }
      if (map) {
        mappedEx = exceptionAdapter.handleException(mappedEx);
      }

      PrintWriter out = response.getWriter();

      //
      // If we have written out data, We are kinda toast in this case.
      // We really want to roll it all back, but we can't, so we opt
      // for the best we can do. For HTML we can do nothing at all.
      //
      if (format == Format.JSON) {
        if (!written) {
          out.write(CSRF_PROTECT);
        }
        //
        // If an exception happened while we were emitting JSON, we want the
        // client to ignore the now-corrupt data structure. 404s and 500s
        // cause the client to prepend /*, so we can effectively erase the
        // bad data by appending a */ here and then serializing the exception
        // info.
        //
        out.write("*/");
        //
        // Unfortunately we can't do the following now. It might be possible
        // in some cases, but we don't want to go there unless we have to.
        //
      }
      if (format == Format.JS || format == Format.CSS) {
        // Make sure js and css doesn't get cached in browser, appcache, etc
        response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR);
      }
      if (format == Format.JSON
          || format == Format.HTML
          || format == Format.JS
          || format == Format.CSS) {
        //
        // We only write out exceptions for HTML or JSON.
        // Seems bogus, but here it is.
        //
        // Start out by cleaning out some settings to ensure we don't
        // check too many things, leading to a circular failure. Note
        // that this is still a bit dangerous, as we seem to have a lot
        // of magic in the serializer.
        //
        // Clear the InstanceStack before trying to serialize the exception since the Throwable has
        // likely
        // rendered the stack inaccurate, and may falsely trigger NoAccessExceptions.
        InstanceStack stack = this.contextService.getCurrentContext().getInstanceStack();
        List<String> list = stack.getStackInfo();
        for (int count = list.size(); count > 0; count--) {
          stack.popInstance(stack.peek());
        }

        serializationService.write(mappedEx, null, out);
        if (format == Format.JSON) {
          out.write("/*ERROR*/");
        }
      }
    } catch (IOException ioe) {
      throw ioe;
    } catch (Throwable death) {
      //
      // Catch any other exception and log it. This is actually kinda bad, because something has
      // gone horribly wrong. We should write out some sort of generic page other than a 404,
      // but at this point, it is unclear what we can do, as stuff is breaking right and left.
      //
      try {
        response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR);
        exceptionAdapter.handleException(death);
        if (!isProductionMode(context.getMode())) {
          response.getWriter().println(death.getMessage());
        }
      } catch (IOException ioe) {
        throw ioe;
      } catch (Throwable doubleDeath) {
        // we are totally hosed.
        if (!isProductionMode(context.getMode())) {
          response.getWriter().println(doubleDeath.getMessage());
        }
      }
    } finally {
      this.contextService.endContext();
    }
  }
    @Override
    public void serialize(Json json, AuraContext ctx) throws IOException {
      json.writeMapBegin();
      json.writeMapEntry("mode", ctx.getMode());

      DefDescriptor<? extends BaseComponentDef> appDesc = ctx.getApplicationDescriptor();
      if (appDesc != null) {
        if (appDesc.getDefType().equals(DefType.APPLICATION)) {
          json.writeMapEntry(
              "app", String.format("%s:%s", appDesc.getNamespace(), appDesc.getName()));
        } else {
          json.writeMapEntry(
              "cmp", String.format("%s:%s", appDesc.getNamespace(), appDesc.getName()));
        }
      }

      if (ctx.getSerializeThemes()) {
        ThemeList themes = ctx.getThemeList();
        if (!themes.isEmpty()) {
          List<String> stringed = Lists.newArrayList();
          for (DefDescriptor<ThemeDef> theme : themes) {
            stringed.add(theme.getQualifiedName());
          }
          json.writeMapEntry("themes", stringed);
        }

        Optional<String> dynamicVarsUid = themes.getActiveDynamicVarsUid();
        if (dynamicVarsUid.isPresent()) {
          json.writeMapEntry("dynamicVarsUid", dynamicVarsUid.get());
        }
      }

      if (ctx.getRequestedLocales() != null) {
        List<String> locales = new ArrayList<>();
        for (Locale locale : ctx.getRequestedLocales()) {
          locales.add(locale.toString());
        }
        json.writeMapEntry("requestedLocales", locales);
      }
      Map<String, String> loadedStrings = Maps.newHashMap();
      Map<DefDescriptor<?>, String> clientLoaded = Maps.newHashMap();
      clientLoaded.putAll(ctx.getClientLoaded());
      for (Map.Entry<DefDescriptor<?>, String> entry : ctx.getLoaded().entrySet()) {
        loadedStrings.put(
            String.format(
                "%s@%s", entry.getKey().getDefType().toString(), entry.getKey().getQualifiedName()),
            entry.getValue());
        clientLoaded.remove(entry.getKey());
      }
      if (forClient) {
        for (DefDescriptor<?> deleted : clientLoaded.keySet()) {
          loadedStrings.put(
              String.format("%s@%s", deleted.getDefType().toString(), deleted.getQualifiedName()),
              DELETED);
        }
      }
      if (loadedStrings.size() > 0) {
        json.writeMapKey("loaded");
        json.writeMap(loadedStrings);
      }
      if (ctx.getSerializeLastMod()) {
        json.writeMapEntry("lastmod", Long.toString(AuraBaseServlet.getLastMod()));
      }

      TestContextAdapter testContextAdapter = Aura.get(TestContextAdapter.class);
      if (testContextAdapter != null) {
        TestContext testContext = testContextAdapter.getTestContext();
        if (testContext != null) {
          json.writeMapEntry("test", testContext.getName());
        }
      }

      if (ctx.getFrameworkUID() != null) {
        json.writeMapEntry("fwuid", ctx.getFrameworkUID());
      }

      if (forClient) {
        // client needs value providers, urls don't
        boolean started = false;

        for (GlobalValueProvider valueProvider : ctx.getGlobalProviders().values()) {
          if (!valueProvider.isEmpty()) {
            if (!started) {
              json.writeMapKey("globalValueProviders");
              json.writeArrayBegin();
              started = true;
            }
            json.writeComma();
            json.writeIndent();
            json.writeMapBegin();
            json.writeMapEntry("type", valueProvider.getValueProviderKey().getPrefix());
            json.writeMapEntry("values", valueProvider.getData());
            json.writeMapEnd();
          }
        }

        if (started) {
          json.writeArrayEnd();
        }

        //
        // Now comes the tricky part, we have to serialize all of the definitions that are
        // required on the client side, and, of all types. This way, we won't have to handle
        // ugly cases of actual definitions nested inside our configs, and, we ensure that
        // all dependencies actually get sent to the client. Note that the 'loaded' set needs
        // to be updated as well, but that needs to happen prior to this.
        //
        Map<DefDescriptor<? extends Definition>, Definition> defMap;

        defMap = ctx.getDefRegistry().filterRegistry(ctx.getPreloadedDefinitions());

        if (defMap.size() > 0) {
          List<Definition> componentDefs = Lists.newArrayList();
          List<Definition> eventDefs = Lists.newArrayList();
          List<Definition> libraryDefs = Lists.newArrayList();

          for (Map.Entry<DefDescriptor<? extends Definition>, Definition> entry :
              defMap.entrySet()) {
            DefDescriptor<? extends Definition> desc = entry.getKey();
            DefType dt = desc.getDefType();
            Definition d = entry.getValue();
            //
            // Ignore defs that ended up not being valid. This is arguably something
            // that the MDR should have done when filtering.
            //
            if (d != null) {
              if (DefType.COMPONENT.equals(dt) || DefType.APPLICATION.equals(dt)) {
                componentDefs.add(d);
              } else if (DefType.EVENT.equals(dt)) {
                eventDefs.add(d);
              } else if (DefType.LIBRARY.equals(dt)) {
                libraryDefs.add(d);
              }
            }
          }
          writeDefs(json, "componentDefs", componentDefs);
          writeDefs(json, "eventDefs", eventDefs);
          writeDefs(json, "libraryDefs", libraryDefs);
        }
        ctx.serializeAsPart(json);
      }
      json.writeMapEnd();
    }
Exemple #8
0
  /**
   * Write out the manifest.
   *
   * <p>This writes out the full manifest for an application so that we can use the AppCache.
   *
   * <p>The manifest contains CSS and JavaScript URLs. These specified resources are copied into the
   * AppCache with the HTML template. When the page is reloaded, the existing manifest is compared
   * to the new manifest. If they are identical, the resources are served from the AppCache.
   * Otherwise, the resources are requested from the server and the AppCache is updated.
   *
   * @param request the request
   * @param response the response
   * @param context the context
   * @throws IOException if unable to write out the response
   */
  @Override
  public void write(HttpServletRequest request, HttpServletResponse response, AuraContext context)
      throws IOException {
    servletUtilAdapter.setNoCache(response);
    try {
      Map<String, Object> attributes = getComponentAttributes(request);
      //
      // First, we make sure that the manifest is enabled.
      //
      if (!manifestUtil.isManifestEnabled(request)) {
        response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        return;
      }
      //
      // Now we validate the cookie, which includes loop detection.
      // this routine sets the response code.
      //
      if (!manifestUtil.checkManifestCookie(request, response)) {
        return;
      }

      boolean appOk = false;

      DefDescriptor<? extends BaseComponentDef> descr = null;
      try {
        descr = context.getApplicationDescriptor();

        if (descr != null) {
          definitionService.updateLoaded(descr);
          appOk = true;
        }
      } catch (QuickFixException qfe) {
        //
        // ignore qfe, since we really don't care... the manifest will be 404ed.
        // This will eventually cause the browser to give up. Note that this case
        // should almost never occur, as it requires the qfe to be introduced between
        // the initial request (which will not set a manifest if it gets a qfe) and
        // the manifest request.
        //
      } catch (ClientOutOfSyncException coose) {
        //
        // In this case, we want to force a reload... A 404 on the manifest is
        // supposed to handle this. we hope that the client will do the right
        // thing, and reload everything. Note that this case really should only
        // happen if the client already has content, and thus should be refreshing
        // However, there are very odd edge cases that we probably can't detect
        // without keeping server side state, such as the case that something
        // is updated between the initial HTML request and the manifest request.
        // Not sure what browsers will do in this case.
        //
      }

      if (!appOk) {
        response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        return;
      }

      //
      // This writes both the app and framework signatures into
      // the manifest, so that if either one changes, the
      // manifest will change. Note that in most cases, we will
      // write these signatures in multiple places, but we just
      // need to make sure that they are in at least one place.
      //
      Map<String, Object> attribs = Maps.newHashMap();
      String appUid = getContextAppUid(context);
      String nonce = configAdapter.getAuraFrameworkNonce();
      // Since we don't get the UID from our URL, we set it here.
      context.setFrameworkUID(nonce);
      attribs.put(LAST_MOD, String.format("app=%s, FW=%s", appUid, nonce));
      attribs.put(UID, appUid);
      StringWriter sw = new StringWriter();

      String resetCssUrl = configAdapter.getResetCssURL();
      if (resetCssUrl != null) {
        sw.write(resetCssUrl);
        sw.write('\n');
      }

      for (String s : servletUtilAdapter.getStyles(context)) {
        sw.write(s);
        sw.write('\n');
      }

      for (String s : servletUtilAdapter.getScripts(context, true, true, attributes)) {
        sw.write(s);
        sw.write('\n');
      }

      // Add locker service safe eval worker url
      String lockerWorkerURL = configAdapter.getLockerWorkerURL();
      if (lockerWorkerURL != null) {
        sw.write(lockerWorkerURL);
        sw.write('\n');
      }

      // Add in any application specific resources
      if (descr != null && descr.getDefType().equals(DefType.APPLICATION)) {
        ApplicationDef def = (ApplicationDef) descr.getDef();
        for (String s : def.getAdditionalAppCacheURLs()) {
          if (s != null) {
            sw.write(s);
            sw.write('\n');
          }
        }
      }

      attribs.put(RESOURCE_URLS, sw.toString());

      DefDescriptor<ComponentDef> tmplDesc =
          definitionService.getDefDescriptor("ui:manifest", ComponentDef.class);
      Component tmpl = instanceService.getInstance(tmplDesc, attribs);
      renderingService.render(tmpl, response.getWriter());
    } catch (Exception e) {
      Aura.getExceptionAdapter().handleException(e);
      // Can't throw exception here: to set manifest OBSOLETE
      response.setStatus(HttpServletResponse.SC_NOT_FOUND);
    }
  }